In this chapter an example is provided to illustrate the main features of the
stream package. In this example a stream class is defined to provide a wrapper for
file-stream which uses the Unicode Line Separator instead of the usual ASCII CR/LF combination to mark the end of lines in the file. Methods are then defined, specializing on the user defined stream class to ensure that it handles reading from and writing to a file correctly.
Streams can be capable of input or output (or both), and may deal with characters or with binary elements. The
stream package provides a number of stream classes with different capabilities from which user defined streams can inherit. In our example the stream must be capable of input and output, and must read characters. The following code defines our stream class appropriately:
The new class,
unicode-ls-stream, has fundamental-character-input-stream and fundamental-character-output-stream as its superclasses, which means it inherits the relevant default character I/O methods. We shall be overriding some of these with more relevant and efficient implementations later.
Note that we have also provided a file-stream slot. When making an instance of
unicode-ls-stream we can create an instance of a Common Lisp file stream in this slot. This allows us to use the Common Lisp file stream functionality for reading from and writing to a file.
We know that the stream will read from a file using
file-stream functionality and that the stream element type will be
character. The following defines a method on stream-element-type to return the correct element type.
Streams can be defined for input only, output only, or both. In our example, the
unicode-ls-stream class needs to be able to read from a file and write to a file, and we therefore defined it to inherit from an input and an output stream class. We could have defined disjoint classes instead, one inheriting from fundamental-character-input-stream and the other from fundamental-character-output-stream. This would have allowed us to rely on the default methods for the direction predicates.
However, given that we have defined one bi-directional stream class, we must define our own methods for the direction predicates. To allow this, the Common Lisp predicates input-stream-p and output-stream-p
The following method for stream-read-char reads a character from the stream. If the character read is a
#\Line-Separator, then the method returns
#\Newline, otherwise the character read is returned. stream-read-char returns
:eof at the end of the file.
There is no need to define a new method for stream-read-line as the default method uses stream-read-char repeatedly to read a line, and our implementation of stream-read-char ensures that this will work.
We also need to make sure that if a
#\Newline is unread, it is unread as a
#\Line-Separator. The following method for
uses the Common Lisp file stream function
unread-char to achieve this.
Finally, although the default methods for
would work for our stream, it is faster to use the functions provided by
file-stream, again using our accessor
The following method for
write-char to write a character to the stream. If the character written to
unicode-ls-stream is a
#\Newline, then the method writes a
#\Line-Separator to the file stream.
To be useful, the
generic functions need to know the number of characters preceding a
#\Line-Separator. However, since the LispWorks file stream records line position only by
#\Newline characters, this information is not available. Hence we define the two generic functions to return
Now that the stream class has been defined, and all the methods relevant to it have been set up, we can create an instance of our user defined stream to test it. The following function takes a filename and optionally a stream direction as its arguments and makes an instance of
unicode-ls-stream. It ensures that the
file-stream slot of the stream contains a Common Lisp
file-stream capable of reading from or writing to a file given by the filename argument.
We now have the required functions and macros to test our user defined stream. The following code uses
config.sys as a source of input to an instance of our stream, and outputs it to the file
unicode-ls.out, changing all occurrences of
#\Line-Separator in the process.
After running the above code, if your load the file
C:\unicode-ls.out into an editor (for example, a LispWorks editor), you can see the line separator used instead of CR/LF. Most editors do not yet recognize the Unicode Line Separator character yet. In some editors it appears as a blank glyph, whereas in the LispWorks editor it appears as
<2028>. In LispWorks you can use
Alt+X What Cursor Position or
Ctrl+X = to identify the unprintable characters.
LispWorks User Guide and Reference Manual - 20 Sep 2017