6.12.10.4 Custom Ports

Custom ports allow the user to provide input and handle output via user-supplied procedures. The most basic of these operates on the level of bytes, calling user-supplied functions to supply bytes for input and accept bytes for output. In Guile, textual ports are built on top of binary ports, encoding and decoding their codepoint sequences from the bytes; the higher-level textual layer for custom ports allows users to deal in characters instead of bytes.

Before using these procedures, import the appropriate module:

(use-modules (ice-9 binary-ports))
(use-modules (ice-9 textual-ports))
Scheme Procedure: make-custom-binary-input-port id read! get-position set-position! close

Return a new custom binary input port named id (a string) whose input is drained by invoking read! and passing it a bytevector, an index where bytes should be written, and the number of bytes to read. The read! procedure must return an integer indicating the number of bytes read, or 0 to indicate the end-of-file.

Optionally, if get-position is not #f, it must be a thunk that will be called when port-position is invoked on the custom binary port and should return an integer indicating the position within the underlying data stream; if get-position was not supplied, the returned port does not support port-position.

Likewise, if set-position! is not #f, it should be a one-argument procedure. When set-port-position! is invoked on the custom binary input port, set-position! is passed an integer indicating the position of the next byte is to read.

Finally, if close is not #f, it must be a thunk. It is invoked when the custom binary input port is closed.

The returned port is fully buffered by default, but its buffering mode can be changed using setvbuf (see Buffering).

Using a custom binary input port, the open-bytevector-input-port procedure (see Bytevector Ports) could be implemented as follows:

(define (open-bytevector-input-port source)
  (define position 0)
  (define length (bytevector-length source))

  (define (read! bv start count)
    (let ((count (min count (- length position))))
      (bytevector-copy! source position
                        bv start count)
      (set! position (+ position count))
      count))

  (define (get-position) position)

  (define (set-position! new-position)
    (set! position new-position))

  (make-custom-binary-input-port "the port" read!
                                  get-position set-position!
                                  #f))

(read (open-bytevector-input-port (string->utf8 "hello")))
⇒ hello
Scheme Procedure: make-custom-binary-output-port id write! get-position set-position! close

Return a new custom binary output port named id (a string) whose output is sunk by invoking write! and passing it a bytevector, an index where bytes should be read from this bytevector, and the number of bytes to be “written”. The write! procedure must return an integer indicating the number of bytes actually written; when it is passed 0 as the number of bytes to write, it should behave as though an end-of-file was sent to the byte sink.

The other arguments are as for make-custom-binary-input-port.

Scheme Procedure: make-custom-binary-input/output-port id read! write! get-position set-position! close

Return a new custom binary input/output port named id (a string). The various arguments are the same as for The other arguments are as for make-custom-binary-input-port and make-custom-binary-output-port. If buffering is enabled on the port, as is the case by default, input will be buffered in both directions; See Buffering. If the set-position! function is provided and not #f, then the port will also be marked as random-access, causing the buffer to be flushed between reads and writes.

Scheme Procedure: make-custom-textual-input-port id read! get-position set-position! close
Scheme Procedure: make-custom-textual-output-port id write! get-position set-position! close
Scheme Procedure: make-custom-textual-input/output-port id read! write! get-position set-position! close

Like their custom binary port counterparts, but for textual ports. Concretely this means that instead of being passed a bytevector, the read function is passed a mutable string to fill, and likewise for the buffer supplied to write. Port positions are still expressed in bytes, however.

If string ports were not supplied with Guile, we could implement them With custom textual ports:

(define (open-string-input-port source)
  (define position 0)
  (define length (string-length source))

  (define (read! dst start count)
    (let ((count (min count (- length position))))
      (string-copy! dst start source position (+ position count))
      (set! position (+ position count))
      count))

  (make-custom-textual-input-port "strport" read! #f #f #f))

(read (open-string-input-port "hello"))