This section describes how to implement a new kind of port using Guile’s
lowest-level, most primitive interfaces. First, load the (ice-9
custom-ports)
module:
(use-modules (ice-9 custom-ports))
Then to make a new port, call make-custom-port
:
Make a new custom port.
See Encoding, for more on #:encoding
and
#:conversion-strategy
.
A port has a number of associated procedures and properties which
collectively implement its behavior. Creating a new custom port mostly
involves writing these procedures, which are passed as keyword arguments
to make-custom-port
.
A port’s #:read
implementation fills read buffers. It should
copy bytes to the supplied bytevector dst, starting at offset
start and continuing for count bytes, and return the number
of bytes that were read, or #f
to indicate that reading any bytes
would block.
A port’s #:write
implementation flushes write buffers to the
mutable store. It should write out bytes from the supplied bytevector
src, starting at offset start and continuing for count
bytes, and return the number of bytes that were written, or #f
to
indicate writing any bytes would block.
If make-custom-port
is passed a #:read
argument, the port
will be an input port. Passing a #:write
argument will make an
output port, and passing both will make an input-output port.
If a port’s #:read
or #:write
method returns #f
,
that indicates that reading or writing would block, and that Guile
should instead poll
on the file descriptor returned by the port’s
#:read-wait-fd
or #:write-wait-fd
method, respectively,
until the operation can complete. See Non-Blocking I/O, for a more
in-depth discussion.
These methods must be implemented if the #:read
or #:write
method can return #f
, and should return a non-negative integer
file descriptor. However they may be called explicitly by a user, for
example to determine if a port may eventually be readable or writable.
If there is no associated file descriptor with the port, they should
return #f
. The default implementation returns #f
.
In rare cases it is useful to be able to know whether data can be read
from a port. For example, if the user inputs 1 2 3
at the
interactive console, after reading and evaluating 1
the console
shouldn’t then print another prompt before reading and evaluating
2
because there is input already waiting. If the port can look
ahead, then it should implement the #:input-waiting?
method,
which returns #t
if input is available, or #f
reading the
next byte would block. The default implementation returns #t
.
Set or get the current byte position of the port. Guile will flush read
and/or write buffers before seeking, as appropriate. The offset
and whence parameters are as for the seek
procedure;
See Random Access.
The #:seek
method returns the byte position after seeking. To
query the current position, #:seek
will be called with an
offset of 0 and SEEK_CUR
for whence. Other values of
offset and/or whence will actually perform the seek. The
#:seek
method should throw an error if the port is not seekable,
which is what the default implementation does.
Truncate the port data to be specified length. Guile will flush buffers beforehand, as appropriate. The default implementation throws an error, indicating that truncation is not supported for this port.
Return #t
if port is open for random access, or #f
otherwise.
Seeking on a random-access port with buffered input, or switching to
writing after reading, will cause the buffered input to be discarded and
Guile will seek the port back the buffered number of bytes. Likewise
seeking on a random-access port with buffered output, or switching to
reading after writing, will flush pending bytes with a call to the
write
procedure. See Buffering.
Indicate to Guile that your port needs this behavior by returning true
from your #:random-access?
method. The default implementation of
this function returns #t
if the port has a #:seek
implementation.
Guile will internally attach buffers to ports. An input port always has a read buffer, and an output port always has a write buffer. See Buffering. A port buffer consists of a bytevector, along with some cursors into that bytevector denoting where to get and put data.
Port implementations generally don’t have to be concerned with
buffering: a port’s #:read
or #:write
method will receive
the buffer’s bytevector as an argument, along with an offset and a
length into that bytevector, and should then either fill or empty that
bytevector. However in some cases, port implementations may be able to
provide an appropriate default buffer size to Guile. For example file
ports implement #:get-natural-buffer-sizes
to let the operating
system inform Guile about the appropriate buffer sizes for the
particular file opened by the port.
This method returns two values, corresponding to the natural read and
write buffer sizes for the ports. The two parameters
read-buf-size and write-buf-size are Guile’s guesses for
what sizes might be good. A custom #:get-natural-buffer-sizes
method could override Guile’s choices, or just pass them on, as the
default implementation does.
Called when the port port is written to out, e.g. via
(write port out)
.
If #:print
is not explicitly supplied, the default implementation
prints something like #<mode:id address>
, where
mode is either input
, output
, or
input-output
, id comes from the #:id
keyword
argument (defaulting to "custom-port"
), and address is a
unique integer associated with the port.
Called when port is closed. It should release any explicitly-managed resources used by the port.
By default, ports that are garbage collected just go away without
closing or flushing any buffered output. If your port needs to release
some external resource like a file descriptor, or needs to make sure
that its internal buffers are flushed even if the port is collected
while it was open, then pass #:close-on-gc? #t
to
make-custom-port
. Note that in that case, the #:close
method will probably be called on a separate thread.
Note that calls to all of these methods can proceed in parallel and
concurrently and from any thread up until the point that the port is
closed. The call to close
will happen when no other method is
running, and no method will be called after the close
method is
called. If your port implementation needs mutual exclusion to prevent
concurrency, it is responsible for locking appropriately.