A stream provides sequential access to an aggregate of data, such as a Dylan sequence or a disk file. Streams grant this access according to a metaphor of reading and writing: elements can be read from streams or written to them.
Streams are represented as Dylan objects, and all are
general instances of the class
<stream>
, which the Streams library
defines.
We say that a stream is established over the data
aggregate. Hence, a stream providing access to the string
"hello world"
is said to be a stream over the
string "hello world"
.
Streams permitting reading operations are called input streams. Input streams allow elements from the underlying data aggregate to be consumed. Conversely, streams permitting writing operations are called output streams. Output streams allow elements to be written to the underlying data aggregate. Streams permitting both kinds of operations are called input-output streams.
The library provides a set of functions for reading elements from an input stream. These functions hide the details of indexing, buffering, and so on. For instance, the function read-element reads a single data element from an input stream.
The following expression binds stream to an input stream
over the string "hello world"
:
let stream = make(<string-stream>, contents: "hello world");
The first invocation of read-element on stream returns the
character 'h', the next invocation 'e', and so on. Once a stream
has been used to consume all the elements of the data, the
stream is said to be at its end. This condition can be tested
with the function stream-at-end?
. The
following code fragment applies function to all elements of the
sequence:
let stream = make(<sequence-stream>, contents: seq); while (~stream-at-end?(stream)) function(read-element(stream)); end;
When all elements of a stream have been read, further
calls to read-element
result in the
<end-of-stream-error>
condition
being signalled. An alternative end-of-stream behavior is to
have a distinguished end-of-stream value returned. You can
supply such an end-of-stream value as a keyword argument to the
various read functions; the value can be any object. Supplying
an end-of-stream value to a read function is more efficient than
asking whether a stream is at its end on every iteration of a
loop.
The library also provides a set of functions for writing
data elements to an output stream. Like the functions that
operate upon input streams, these functions hide the details of
indexing, growing an underlying sequence, buffering for a file,
and so on. For instance, the function
write-element
writes a single data element
to an output stream.
The following forms bind stream to an output stream over
an empty string and create the string "I
see!"
, using the function
stream-contents
to access all of the
stream's elements.
let stream = make(<byte-string-stream>, direction: #"output"); write-element(stream, 'I'); write-element(stream, ' '); write(stream, "see"); write-element(stream, '!'); stream-contents(stream);
Calling write
on a sequence has the
same effect as calling write-element
on all
the elements of the sequence. However, it is not required that
write
be implemented directly in terms of
write-element
; it might be implemented more
efficiently, especially for buffered streams.
Some streams are positionable; that is, they permit random
access to their elements. Postionable streams allow you to set
the position at which the stream will be accessed by the next
operation. The following example uses positioning to return the
character 'w'
from a stream over the string
"hello world"
:
let stream = make(<string-stream>, contents: "hello world"); stream-position(stream) := 6; read-element(stream);
The following example returns a string, but the contents of the first ten characters are undefined:
let stream = make(<string-stream>, direction: #"output"); adjust-stream-position(stream, 10); write(stream, "whoa!"); stream-contents(stream);
You can request a sequence containing all of the elements
of a positionable stream by calling
stream-contents
on it. The sequence
returned never shares structure with any underlying sequence
that might be used in future by the stream. For instance, the
string returned by calling stream-contents
on an output <string-stream>
will
not be the same string as that being used to represent the
string stream.
When making an input <string-stream>
, you can cause
the stream to produce elements from any subsequence of the
supplied string. For example:
read-to-end(make(<string-stream>, contents: "hello there, world", start: 6, end: 11));
This example evaluates to "there". The interval (start, end) includes the index start but excludes the index end. This is consistent with standard Dylan functions over sequences, such as copy-sequence. The read-to-end function is one of a number of convenient utility functions for operating on streams and returns all the elements up to the end of the stream from the stream's current position.