6.12.10.7 Low-Level Custom Ports

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:

Scheme Procedure: make-custom-port [#:read] [#:write] [#:read-wait-fd] [#:write-wait-fd] [#:input-waiting?] [#:seek] [#:random-access?] [#:get-natural-buffer-sizes] [#:id] [#:print] [#:close] [#:close-on-gc?] [#:truncate] [#:encoding] [#:conversion-strategy]

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.

Scheme Port Method: #:read port dst start count

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.

Scheme Port Method: #:write port src start count

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.

Scheme Port Method: #:read-wait-fd port
Scheme Port Method: #:write-wait-fd 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.

Scheme Port Method: #:input-waiting? port

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.

Scheme Port Method: #:seek port offset whence

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.

Scheme Port Method: #:truncate port

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.

Scheme Port Method: #:random-access? 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.

Scheme Port Method: #:get-natural-buffer-sizes read-buf-size write-buf-size

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.

Scheme Port Method: #:print port out

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.

Scheme Port Method: #:close 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.