Suspendable Soft Ports

Guile has the ability to define a soft port e.g. create a port with scheme code handlers that are executed in stead of C code when basic operations on the port is done. This works quite ok as long as one does not trip up due to the continuation barrier that comes with executing C code that executes Scheme code. The whole idea with this project was to design a soft port and hook it into the code similarly as in the (ice-9 suspendable-port).

This project contains 2 files. First we have

soft-port.scm

This contains the basic soft port data structure (currently a class) and all the lower level routines needed to make suspendable-port work for the soft port. The other module is

soft-suspenable-ports.scm

This is basically a copy of suspendable-port.scm but all routines now have two variants, one for the soft port using the lower level routines in soft-port.scm and one that is just the old version for normal port. Then all interface code for ports are just dispatching between those two variants. Finally the functions that instantiates and removes the new versions of the port utilities are exported so that on can at will use these port interface utilities than the standard ones.

So, we need some information to create soft-ports. to create it use any of the following functions,

(define* (make-soft-input/output-port
      name read! write! close
      #:key
      (strategy     #f)
      (encoding     #f)
      (flags        #f)
      (seek         #f)
      (subport      #f)
      (buffering?   #f)
      (read-buffer  #f)
      (write-buffer #f)
      (aux-buffer   #f))
  ...)

(define* (make-soft-output-port
      name write! close
      #:key
      (strategy     #f)
      (encoding     #f)
      (flags        #f)
      (seek         #f)
      (subport      #f)
      (buffering?   #f)     
      (write-buffer #f)
      (aux-buffer   #f))

  ...)

(define* (make-soft-input-port
      name read! close
      #:key
      (strategy     #f)
      (encoding     #f)
      (flags        #f)
      (seek         #f)
      (subport      #f)
      (buffering?   #f)     
      (read-buffer  #f))

 ...)

The name field is just to get a name printout of the soft port for better traceability in debugging and using these ports. read! and write! has the same semantics as for the old port interface and similarly for the close field. read! and write! can be false and in that case one uses the subport read and write functionality. strategy and encoding as to do with handling strings and if not specified or false will use the suborts encoding data or if that is false, the default encoding. flags has no functionality atm. It is possible to define a seek function and if buffering is true it will either be using supplied buffers or if #t make a default new buffer or else use buffers from the subport port. Finally the subport allows one to define a underlying port object from which many low level routines will use in stead of the soft port. If this is a soft port it will use that soft port's subport as subport.

The read! function are the form,

(read! dst-bv start count)

e.g. read are most count bytes into the dst-bv filling in from the start position.

The write! function are the form

(write! src-bv start count)

e.g. write count bytes from src-bv starting from the 'start' position

The close function is of the form

(close)

You can supply a false in stead of the close function in case close is trivial e.g. does not need to do any cleanup operation.

And the seek function follows the standard seek but with arguments

(seek pos mode)

As an example, we could use tls to create a new port p2 from a port p but when we close it we need to close both p and p2 then we can create a soft port that does this with,

(make-soft-input/output-port "tls" #f #f (lambda () (close p) (close p2))
                             #:subport p2)

Here is a buffered soft port reading from a byte-vector

(define (mk-bvport bv)
   (define pt 0)
   (define n  (bytevector-length bv))
   (define (read! src start count)              
     (let ((m (min count (- n pt))))
       (bytevector-copy! bv pt src start m)
       (set! pt (+ pt m))
       m))

   ;; We only do seek cur
   (define (seek i _)
      (set pt (min n (+ pt i))))

   (make-soft-input-port "bv" read! #f #:buffering? #t #:seek seek))

Here is a soft port that scrambles the bytes

(define (mk-scramble-port port x)
   (define read% (port-read port))

   (define (read! src start count)
      (let ((read (read% port src start count)))
        (let lp ((i 0) (j start))
          (when (< i read)
            (bytevector-u8-set! src j (logxor (bytevector-u8-ref src j) x))
            (lp (+ i 1) (+ j 1))))
        read))

   ;; no need to buffer this port as all operations are suspendable already
   ;; and many low level routines dispatches back to the in port
   (make-soft-input-port "scramble" read! #f #:subport port))

Nothing is tested yet but the intention is that you should be able to create soft ports that are suspendable like this.

links

social