Quoting Rudi Schlatte (rudi@constantly.at): | Add a class 'device-stream' which is a subclass of stream of metaclass | standard-class. An instance of a subclass of device-stream is meant | to manage an "underlying device", e.g. a file or socket.
OK, so there will be at least two objects for each Common Lisp streams, the stream itself and the device beneath it. That is nice because it allows applications to call device layer functions directly without creating a full streams first and then "bypassing" the upper layers.
OTOH, I am unclear about how the device and the buffering layer would interact in such a scheme for streams more complicated than file-streams. We discussed string-stream-like approaches where buffers need to be exchanged after writing them to the device, and with this approach the device methods would need to access the buffering object, a layering violation? Perhaps a question that can only be answered when there is also a proposed design for the buffering layer.
| @smalldisplay | ;;;; rudi (2004-07-09): we should also specify the conditions that are | ;;;; thrown, if any. My gut feeling would be to work with return | ;;;; values only and throw conditions farther up, what do you think? | @end smalldisplay
So EOF is not a condition here because it is not really an exceptional situation, but rather expected. Fine. Actual errors, however, should be conditions, I think. (- -10 errno) does not look very lispy to me...
| @defun device-open device &rest initargs @result{} result | | @var{device}: an instance of device-stream | | @var{initargs}: options specific to the actual type of @var{device} | | @var{result}: a generalized boolean | | @code{device-open} performs the device-specific operations to open the | underlying device, taking any needed information from @var{initargs}. | @var{result} is true if the device could be successfully opened, false | if not. | @end defun
Can you elaborate on that? Given that CLOS is used here anyway, why is initialization of the stream not done by INITIALIZE-INSTANCE and friends? I know that simple streams have a DEVICE-OPEN method, but I am unclear about why that was invented, too.
Is it necessary to be able to create streams and open them only later? Or can streams be re-opened?
| @defun device-close device | | @var{device}: an instance of device-stream | | @code{device-close} closes the underlying device. | @end defun
Might need an ABORT argument?
| @defun device-read device buffer start end blocking @result{} result | @defun device-write device buffer start end blocking @result{} result
Sounds good.
| @defun device-clear-input device | | @var{device}: an instance of @code{device-stream} | | @code{device-clear-input} performs any device-specific operations necessary | to discard any input pending on the underlying device, including | clearing any os-level buffers or similar. | @end defun
The only correct implementation I can imagine for this function with Unix files is to loop in read() until nothing more is returned. Bad idea with, say, /dev/random, because it will loop forever. Is this really what is meant? If not, can we make it more precise or just drop it completely?
| @defun device-clear-output device | | @var{device}: an instance of @code{device-stream} | | @code{device-clear-output} performs any device-specific operations necessary | to discard any output pending on the underlying device, including | clearing any os-level buffers or similar. | @end defun
Similar question as for clear-input: This sounds like the device layer implementation of CLEAR-OUTPUT. Taking Unix file descriptors an an example, how would this work? (If there is no reasonable implementation of this on current operating systems, why not assume that CLEAR-OUTPUT flushes the higher-level buffer, but does not reach the device layer at all?)
| @defun device-flush-output device blocking | | @smalldisplay | ;;;; rudi (2004-10-09): device-flush-output instead of | ;;;; device-finish-output, device-force-output since the blocking | ;;;; parameter exists everywhere else as well (it seems more in line | ;;;; with the other methods, but I don't insist on this change) | @end smalldisplay
| @var{device}: an instance of @code{device-stream} | | @var{blocking}: a generalized boolean | | @code{device-flush-output} performs any device-specific operations | necessary to flush any output pending on the underlying device. If | @var{blocking} is true, @code{device-flush-output} will make a best effort to | block until the underlying device confirms completion of the output. | If @var{blocking} is false, @code{device-finish-output} returns immediately. | @end defun
To clarify, with a unix file, this should be fsync(),right?
(I have always assumed that FORCE-OUTPUT flushes a stream's buffer, and FINISH-OUTPUT does more: It calls fsync(). In reality, the Lisps I have looked at just flush the buffer for both functions. :()
On the BLOCKING argument: Not sure. Assuming it is possible, what good does syncing do when you do not wait for it to complete?
| @defun (setf device-file-position) device new-position @result{} result
@defun (setf device-file-position) new-position device @result{} result
Wishlist item: I have often missed a portable function FILE-TRUNCATE. Would such a function fit into this interface?
David