~ chicken-core (chicken-5) b00218d724bbcd1006a94019eb0ebc90ebdc2584


commit b00218d724bbcd1006a94019eb0ebc90ebdc2584
Author:     Evan Hanson <evhan@foldling.org>
AuthorDate: Thu Jun 30 20:09:49 2016 +1200
Commit:     Peter Bex <peter@more-magic.net>
CommitDate: Sat Jul 9 16:26:41 2016 +0200

    Add new `make-bidirectional-port` procedure to ports unit
    
    This allows an input and output port to be combined into a single
    bidirectional port.
    
    Signed-off-by: Peter Bex <peter@more-magic.net>

diff --git a/NEWS b/NEWS
index 775ee576..9c24d815 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,9 @@
   - `with-error-output-to-port' from the ports module has been renamed
     to the more common `with-error-to-port', and `with-error-to-string'
     has been added for completeness (thanks to Michael Silver).
+  - A new `make-bidirectional-port' procedure has been added to the
+    ports unit that will combine separate input- and output- ports into
+    a single bidirectional port.
   - New `input-port-open?` and `output-port-open?` procedures have been
     added for testing whether a port is open in a specific direction.
 
diff --git a/library.scm b/library.scm
index be9c61ae..c222fd7e 100644
--- a/library.scm
+++ b/library.scm
@@ -2772,7 +2772,7 @@ EOF
     (let ((direction (if inp 1 2)))
       (when (##core#inline "C_port_openp" port direction)
 	(##sys#setislot port 8 (fxand (##sys#slot port 8) (fxnot direction)))
-	((##sys#slot (##sys#slot port 2) 4) port inp))))
+	((##sys#slot (##sys#slot port 2) 4) port direction))))
 
   (set! open-input-file (lambda (name . mode) (open name #t mode 'open-input-file)))
   (set! open-output-file (lambda (name . mode) (open name #f mode 'open-output-file)))
diff --git a/manual/Unit ports b/manual/Unit ports
index 9e4d22e4..fc8fde46 100644
--- a/manual/Unit ports	
+++ b/manual/Unit ports	
@@ -167,6 +167,15 @@ as {{(READ FROM)}} and {{(WRITE X TO)}}, respectively.
 
 === Funky ports
 
+==== make-bidirectional-port
+
+<procedure>(make-bidirectional-port INPUT-PORT OUTPUT-PORT)</procedure>
+
+Returns a joint input/output port that proxies port operations to the
+given {{INPUT-PORT}} and {{OUTPUT-PORT}}, respectively. This port
+satisfies both {{input-port?}} and {{output-port?}}, and its two
+directions may be closed independently.
+
 ==== make-broadcast-port
 
 <procedure>(make-broadcast-port PORT ...)</procedure>
diff --git a/ports.scm b/ports.scm
index 33390c47..0396423e 100644
--- a/ports.scm
+++ b/ports.scm
@@ -45,6 +45,7 @@
    port-for-each
    port-map
    port-fold
+   make-bidirectional-port
    make-broadcast-port
    make-concatenated-port
    with-error-to-port
@@ -289,4 +290,32 @@
       (##sys#set-port-data! port data) 
       port) ) )
 
+(define (make-bidirectional-port i o)
+  (let* ((class (vector
+		 (lambda (_)             ; read-char
+		   (read-char i))
+		 (lambda (_)             ; peek-char
+		   (peek-char i))
+		 (lambda (_ c)           ; write-char
+		   (write-char c o))
+		 (lambda (_ s)           ; write-string
+		   (write-string s #f o))
+		 (lambda (_ d)           ; close
+		   (case d
+		     ((1) (close-input-port i))
+		     ((2) (close-output-port o))))
+		 (lambda (_)             ; flush-output
+		   (flush-output o))
+		 (lambda (_)             ; char-ready?
+		   (char-ready? i))
+		 (lambda (_ n d s)       ; read-string!
+		   (read-string! n d i s))
+		 (lambda (_ l)           ; read-line
+		   (read-line i l))
+		 (lambda ()              ; read-buffered
+		   (read-buffered i))))
+	 (port (##sys#make-port 3 class "(bidirectional)" 'bidirectional)))
+    (##sys#set-port-data! port (vector #f))
+    port))
+
 )
diff --git a/tests/port-tests.scm b/tests/port-tests.scm
index 2fc19a08..0f5fdbbb 100644
--- a/tests/port-tests.scm
+++ b/tests/port-tests.scm
@@ -131,6 +131,39 @@ EOF
   (assert (not (output-port-open? p)))
   (assert (= n 1)))
 
+;; bidirectional ports
+
+(let* ((b (string))
+       (w (lambda (s)
+	    (set! b (string-append b s))))
+       (e (lambda ()
+	    (positive? (string-length b))))
+       (r (lambda ()
+	    (let ((s b))
+	      (set! b (substring s 1))
+	      (string-ref s 0))))
+       (i (make-input-port r e void))
+       (o (make-output-port w void))
+       (p (make-bidirectional-port i o)))
+  (assert (input-port? p))
+  (assert (output-port? p))
+  (assert (input-port-open? p))
+  (assert (output-port-open? p))
+  (display "quartz ruby" p)
+  (newline p)
+  (assert (equal? (read p) 'quartz))
+  (assert (equal? (read i) 'ruby))
+  (display "emerald topaz" p)
+  (newline p)
+  (close-output-port p)
+  (assert (not (output-port-open? o)))
+  (assert (not (output-port-open? p)))
+  (assert (equal? (read p) 'emerald))
+  (assert (equal? (read i) 'topaz))
+  (close-input-port p)
+  (assert (not (input-port-open? i)))
+  (assert (not (input-port-open? p))))
+
 ;; fill buffers
 (with-input-from-file "compiler.scm" read-string)
 
diff --git a/types.db b/types.db
index 3c3932c0..06e292f6 100644
--- a/types.db
+++ b/types.db
@@ -1849,6 +1849,7 @@
  (forall (a b) (#(procedure #:enforce) chicken.ports#port-map ((procedure (a) b) (procedure () a)) (list-of b))))
 
 (chicken.ports#port-fold (#(procedure #:enforce) chicken.ports#port-fold ((procedure (* *) *) * (procedure () *)) *))
+(chicken.ports#make-bidirectional-port (#(procedure #:clean #:enforce) chicken.ports#make-bidirectional-port (input-port output-port) (refine (input output) port)))
 (chicken.ports#make-broadcast-port (#(procedure #:clean #:enforce) chicken.ports#make-broadcast-port (#!rest output-port) output-port))
 (chicken.ports#make-concatenated-port (#(procedure #:clean #:enforce) chicken.ports#make-concatenated-port (port #!rest input-port) input-port))
 (chicken.ports#with-error-to-port (#(procedure #:enforce) chicken.ports#with-error-to-port (output-port (procedure () . *)) . *))
Trap