6.12.10.5 Soft Ports

Soft ports are what Guile had before it had custom binary and textual ports, and allow for customizable textual input and output.

We recommend soft ports over R6RS custom textual ports because they are easier to use while also being more expressive. R6RS custom textual ports operate under the principle that a port has a mutable string buffer, and this is reflected in the read and write procedures which take a buffer, offset, and length. However in Guile as all ports have a byte buffer rather than some having a string buffer, the R6RS interface imposes overhead and complexity.

Additionally, and unlike the R6RS interfaces, make-soft-port from the (ice-9 soft-ports) module accepts keyword arguments, allowing for its functionality to be extended over time.

If you find yourself needing more power, notably the ability to seek, probably you want to use low-level custom ports. See Low-Level Custom Ports.

(use-modules (ice-9 soft-ports))
Scheme Procedure: make-soft-port [#:id] [#:read-string] [#:write-string] [#:input-waiting?] [#:close] [#:close-on-gc?]

Return a new port. If the read-string keyword argument is present, the port will be an input port. If write-string is present, the port will be an output port. If both are supplied, the port will be open for input and output.

When the port’s internal buffers are empty, read-string will be called with no arguments, and should return a string, or #f to indicate end-of-stream. Similarly when a port flushes its write buffer, the characters in that buffer will be passed to the write-string procedure as its single argument. write-string returns unspecified values.

If supplied, input-waiting? should return #t if the soft port has input which would be returned directly by read-string.

If supplied, close will be called when the port is closed, with no arguments. If close-on-gc? is #t, close will additionally be called when the port becomes unreachable, after flushing any pending write buffers.

With soft ports, the open-string-input-port example from the previous section is more simple:

(define (open-string-input-port source)
  (define already-read? #f)

  (define (read-string)
    (cond
     (already-read? "")
     (else
      (set! already-read? #t)
      source)))

  (make-soft-port #:id "strport" #:read-string read-string))

Note that there was an earlier form of make-soft-port which was exposed in Guile’s default environment, and which is still there. Its interface is more clumsy and its users historically expect unbuffered input. This interface will be deprecated, but we document it here.

Scheme Procedure: deprecated-make-soft-port pv modes

Return a port capable of receiving or delivering characters as specified by the modes string (see open-file). pv must be a vector of length 5 or 6. Its components are as follows:

  1. procedure accepting one character for output
  2. procedure accepting a string for output
  3. thunk for flushing output
  4. thunk for getting one character
  5. thunk for closing port (not by garbage collection)
  6. (if present and not #f) thunk for computing the number of characters that can be read from the port without blocking.

For an output-only port only elements 0, 1, 2, and 4 need be procedures. For an input-only port only elements 3 and 4 need be procedures. Thunks 2 and 4 can instead be #f if there is no useful operation for them to perform.

If thunk 3 returns #f or an eof-object (see eof-object? in The Revised^5 Report on Scheme) it indicates that the port has reached end-of-file. For example:

(define stdout (current-output-port))
(define p (deprecated-make-soft-port
           (vector
            (lambda (c) (write c stdout))
            (lambda (s) (display s stdout))
            (lambda () (display "." stdout))
            (lambda () (char-upcase (read-char)))
            (lambda () (display "@" stdout)))
           "rw"))

(write p p) ⇒ #<input-output: soft 8081e20>