;;; Ports, implemented in Scheme ;;; Copyright (C) 2016, 2019 Free Software Foundation, Inc. ;;; ;;; This library is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU Lesser General Public License as ;;; published by the Free Software Foundation, either version 3 of the ;;; License, or (at your option) any later version. ;;; ;;; This library is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;; Lesser General Public License for more details. ;;; ;;; You should have received a copy of the GNU Lesser General Public ;;; License along with this program. If not, see ;;; . ;;; Commentary: ;;; ;;; We would like to be able to implement green threads using delimited ;;; continuations. When a green thread would block on I/O, it should ;;; suspend and arrange to be resumed when it can make progress. ;;; ;;; The problem is that the ports code is written in C. A delimited ;;; continuation that captures a C activation can't be resumed, because ;;; Guile doesn't know about the internal structure of the C activation ;;; (stack frame) and so can't compose it with the current continuation. ;;; For that reason, to implement this desired future, we have to ;;; implement ports in Scheme. ;;; ;;; If Scheme were fast enough, we would just implement ports in Scheme ;;; early in Guile's boot, and that would be that. However currently ;;; that's not the case: character-by-character I/O is about three or ;;; four times slower in Scheme than in C. This is mostly bytecode ;;; overhead, though there are some ways that compiler improvements ;;; could help us too. ;;; ;;; Note that the difference between Scheme and C is much less for ;;; batched operations, like read-bytes or read-line. ;;; ;;; So the upshot is that we need to keep the C I/O routines around for ;;; performance reasons. We can still have our Scheme routines ;;; available as a module, though, for use by people working with green ;;; threads. That's this module. People that want green threads can ;;; even replace the core bindings, which enables green threading over ;;; other generic routines like the HTTP server. ;;; ;;; Code: (define-module (ice-9 suspendable-ports) #:use-module (rnrs bytevectors) #:use-module (ice-9 ports internal) #:use-module (ice-9 match) #:export (current-read-waiter current-write-waiter install-suspendable-ports! uninstall-suspendable-ports!)) (define (default-read-waiter port) (port-poll port "r")) (define (default-write-waiter port) (port-poll port "w")) (define current-read-waiter (make-parameter default-read-waiter)) (define current-write-waiter (make-parameter default-write-waiter)) (define (wait-for-readable port) ((current-read-waiter) port)) (define (wait-for-writable port) ((current-write-waiter) port)) (define (read-bytes port dst start count) (cond (((port-read port) port dst start count) => (lambda (read) (unless (<= 0 read count) (error "bad return from port read function" read)) read)) (else (wait-for-readable port) (read-bytes port dst start count)))) (define (write-bytes port src start count) (cond (((port-write port) port src start count) => (lambda (written) (unless (<= 0 written count) (error "bad return from port write function" written)) (when (< written count) (write-bytes port src (+ start written) (- count written))))) (else (wait-for-writable port) (write-bytes port src start count)))) (define (flush-input port) (let* ((buf (port-read-buffer port)) (cur (port-buffer-cur buf)) (end (port-buffer-end buf))) (when (< cur end) (set-port-buffer-cur! buf 0) (set-port-buffer-end! buf 0) (seek port (- cur end) SEEK_CUR)))) (define (flush-output port) (let* ((buf (port-write-buffer port)) (cur (port-buffer-cur buf)) (end (port-buffer-end buf))) (when (< cur end) ;; Update cursors before attempting to write, assuming that I/O ;; errors are sticky. That way if the write throws an error, ;; causing the computation to abort, and possibly causing the port ;; to be collected by GC when it's open, any subsequent close-port ;; or force-output won't signal *another* error. (set-port-buffer-cur! buf 0) (set-port-buffer-end! buf 0) (write-bytes port (port-buffer-bytevector buf) cur (- end cur))))) (define utf8-bom #vu8(#xEF #xBB #xBF)) (define utf16be-bom #vu8(#xFE #xFF)) (define utf16le-bom #vu8(#xFF #xFE)) (define utf32be-bom #vu8(#x00 #x00 #xFE #xFF)) (define utf32le-bom #vu8(#xFF #xFE #x00 #x00)) (define (clear-stream-start-for-bom-read port io-mode) (define (maybe-consume-bom bom) (and (eq? (peek-byte port) (bytevector-u8-ref bom 0)) (call-with-values (lambda () (fill-input port (bytevector-length bom))) (lambda (buf cur buffered) (and (<= (bytevector-length bom) buffered) (let ((bv (port-buffer-bytevector buf))) (let lp ((i 1)) (if (= i (bytevector-length bom)) (begin (set-port-buffer-cur! buf (+ cur i)) #t) (and (eq? (bytevector-u8-ref bv (+ cur i)) (bytevector-u8-ref bom i)) (lp (1+ i))))))))))) (when (and (port-clear-stream-start-for-bom-read port) (eq? io-mode 'text)) (case (%port-encoding port) ((UTF-8) (maybe-consume-bom utf8-bom)) ((UTF-16) (cond ((maybe-consume-bom utf16le-bom) (specialize-port-encoding! port 'UTF-16LE)) (else (maybe-consume-bom utf16be-bom) (specialize-port-encoding! port 'UTF-16BE)))) ((UTF-32) (cond ((maybe-consume-bom utf32le-bom) (specialize-port-encoding! port 'UTF-32LE)) (else (maybe-consume-bom utf32be-bom) (specialize-port-encoding! port 'UTF-32BE))))))) (define* (fill-input port #:optional (minimum-buffering 1) (io-mode 'text)) (clear-stream-start-for-bom-read port io-mode) (let* ((buf (port-read-buffer port)) (cur (port-buffer-cur buf)) (buffered (max (- (port-buffer-end buf) cur) 0))) (cond ((or (<= minimum-buffering buffered) (port-buffer-has-eof? buf)) (values buf cur buffered)) (else (unless (input-port? port) (error "not an input port" port)) (when (port-random-access? port) (flush-output port)) (let ((bv (port-buffer-bytevector buf))) (cond ((< (bytevector-length bv) minimum-buffering) (expand-port-read-buffer! port minimum-buffering) (fill-input port minimum-buffering)) (else (when (< 0 cur) (bytevector-copy! bv cur bv 0 buffered) (set-port-buffer-cur! buf 0) (set-port-buffer-end! buf buffered)) (let ((buffering (max (port-read-buffering port) minimum-buffering))) (let lp ((buffered buffered)) (let* ((count (- buffering buffered)) (read (read-bytes port bv buffered count))) (cond ((zero? read) (set-port-buffer-has-eof?! buf #t) (values buf 0 buffered)) (else (let ((buffered (+ buffered read))) (set-port-buffer-end! buf buffered) (if (< buffered minimum-buffering) (lp buffered) (values buf 0 buffered))))))))))))))) (define* (force-output #:optional (port (current-output-port))) (unless (and (output-port? port) (not (port-closed? port))) (error "not an open output port" port)) (flush-output port)) (define close-port (let ((%close-port (@ (guile) close-port))) (lambda (port) (cond ((port-closed? port) #f) (else (when (output-port? port) (flush-output port)) (%close-port port)))))) (define-inlinable (peek-bytes port count kfast kslow) (let* ((buf (port-read-buffer port)) (cur (port-buffer-cur buf)) (buffered (- (port-buffer-end buf) cur))) (if (<= count buffered) (kfast buf (port-buffer-bytevector buf) cur buffered) (call-with-values (lambda () (fill-input port count)) (lambda (buf cur buffered) (kslow buf (port-buffer-bytevector buf) cur buffered)))))) (define (peek-byte port) (peek-bytes port 1 (lambda (buf bv cur buffered) (bytevector-u8-ref bv cur)) (lambda (buf bv cur buffered) (and (> buffered 0) (bytevector-u8-ref bv cur))))) (define* (lookahead-u8 port) (define (fast-path buf bv cur buffered) (bytevector-u8-ref bv cur)) (define (slow-path buf bv cur buffered) (if (zero? buffered) the-eof-object (fast-path buf bv cur buffered))) (peek-bytes port 1 fast-path slow-path)) (define* (get-u8 port) (define (fast-path buf bv cur buffered) (set-port-buffer-cur! buf (1+ cur)) (bytevector-u8-ref bv cur)) (define (slow-path buf bv cur buffered) (if (zero? buffered) (begin (set-port-buffer-has-eof?! buf #f) the-eof-object) (fast-path buf bv cur buffered))) (peek-bytes port 1 fast-path slow-path)) (define (get-bytevector-n! port bv start count) (define (port-buffer-take! pos buf cur to-copy) (bytevector-copy! (port-buffer-bytevector buf) cur bv pos to-copy) (set-port-buffer-cur! buf (+ cur to-copy)) (+ pos to-copy)) (define (take-already-buffered) (let* ((buf (port-read-buffer port)) (cur (port-buffer-cur buf)) (buffered (max (- (port-buffer-end buf) cur) 0))) (port-buffer-take! start buf cur (min count buffered)))) (define (buffer-and-fill pos) (call-with-values (lambda () (fill-input port 1 'binary)) (lambda (buf cur buffered) (if (zero? buffered) ;; We found EOF, which is marked in the port read buffer. ;; If we haven't read any bytes yet, clear the EOF from the ;; buffer and return it. Otherwise return the number of ;; bytes that we have read. (if (= pos start) (begin (set-port-buffer-has-eof?! buf #f) the-eof-object) (- pos start)) (let ((pos (port-buffer-take! pos buf cur (min (- (+ start count) pos) buffered)))) (if (= pos (+ start count)) count (buffer-and-fill pos))))))) (define (fill-directly pos) (when (port-random-access? port) (flush-output port)) (port-clear-stream-start-for-bom-read port) (let lp ((pos pos)) (let ((read (read-bytes port bv pos (- (+ start count) pos)))) (cond ((= (+ pos read) (+ start count)) count) ((zero? read) ;; We found EOF. If we haven't read any bytes yet, return ;; EOF. Otherwise save the EOF in the port read buffer. (if (= pos start) the-eof-object (begin (set-port-buffer-has-eof?! (port-read-buffer port) #t) (- pos start)))) (else (lp (+ pos read))))))) (let ((pos (take-already-buffered))) (cond ((= pos (+ start count)) count) ((< (- (+ start count) pos) (port-read-buffering port)) (buffer-and-fill pos)) (else (fill-directly pos))))) (define (get-bytevector-n port count) (let* ((bv (make-bytevector count)) (result (get-bytevector-n! port bv 0 count))) (cond ((eof-object? result) result) ((= result count) bv) (else (let ((bv* (make-bytevector result))) (bytevector-copy! bv 0 bv* 0 result) bv*))))) (define (get-bytevector-some port) (call-with-values (lambda () (fill-input port 1 'binary)) (lambda (buf cur buffered) (if (zero? buffered) (begin (set-port-buffer-has-eof?! buf #f) the-eof-object) (let ((result (make-bytevector buffered))) (bytevector-copy! (port-buffer-bytevector buf) cur result 0 buffered) (set-port-buffer-cur! buf (+ cur buffered)) result))))) (define (get-bytevector-some! port bv start count) (if (zero? count) 0 (call-with-values (lambda () (fill-input port 1 'binary)) (lambda (buf cur buffered) (if (zero? buffered) (begin (set-port-buffer-has-eof?! buf #f) the-eof-object) (let ((transfer-size (min count buffered))) (bytevector-copy! (port-buffer-bytevector buf) cur bv start transfer-size) (set-port-buffer-cur! buf (+ cur transfer-size)) transfer-size)))))) (define (put-u8 port byte) (let* ((buf (port-write-buffer port)) (bv (port-buffer-bytevector buf)) (end (port-buffer-end buf))) (unless (<= 0 end (bytevector-length bv)) (error "not an output port" port)) (when (and (eq? (port-buffer-cur buf) end) (port-random-access? port)) (flush-input port)) (cond ((= end (bytevector-length bv)) ;; Multiple threads racing; race to flush, then retry. (flush-output port) (put-u8 port byte)) (else (bytevector-u8-set! bv end byte) (set-port-buffer-end! buf (1+ end)) (when (= (1+ end) (bytevector-length bv)) (flush-output port)))))) (define* (put-bytevector port src #:optional (start 0) (count (- (bytevector-length src) start))) (unless (<= 0 start (+ start count) (bytevector-length src)) (error "invalid start/count" start count)) (let* ((buf (port-write-buffer port)) (bv (port-buffer-bytevector buf)) (size (bytevector-length bv)) (cur (port-buffer-cur buf)) (end (port-buffer-end buf)) (buffered (max (- end cur) 0))) (when (and (eq? cur end) (port-random-access? port)) (flush-input port)) (cond ((<= size count) ;; The write won't fit in the buffer at all; write directly. ;; Write directly. Flush write buffer first if needed. (when (< cur end) (flush-output port)) (write-bytes port src start count)) ((< (- size buffered) count) ;; The write won't fit into the buffer along with what's already ;; buffered. Flush and fill. (flush-output port) (set-port-buffer-end! buf count) (bytevector-copy! src start bv 0 count)) (else ;; The write will fit in the buffer, but we need to shuffle the ;; already-buffered bytes (if any) down. (set-port-buffer-cur! buf 0) (set-port-buffer-end! buf (+ buffered count)) (bytevector-copy! bv cur bv 0 buffered) (bytevector-copy! src start bv buffered count) ;; If the buffer completely fills, we flush. (when (= (+ buffered count) size) (flush-output port)))))) (define (decoding-error subr port) ;; GNU definition; fixme? (define EILSEQ 84) (throw 'decoding-error subr "input decoding error" EILSEQ port)) (define-inlinable (decode-utf8 bv start avail u8_0 kt kf) (cond ((< u8_0 #x80) (kt (integer->char u8_0) 1)) ((and (<= #xc2 u8_0 #xdf) (<= 2 avail)) (let ((u8_1 (bytevector-u8-ref bv (1+ start)))) (if (= (logand u8_1 #xc0) #x80) (kt (integer->char (logior (ash (logand u8_0 #x1f) 6) (logand u8_1 #x3f))) 2) (kf)))) ((and (= (logand u8_0 #xf0) #xe0) (<= 3 avail)) (let ((u8_1 (bytevector-u8-ref bv (+ start 1))) (u8_2 (bytevector-u8-ref bv (+ start 2)))) (if (and (= (logand u8_1 #xc0) #x80) (= (logand u8_2 #xc0) #x80) (case u8_0 ((#xe0) (>= u8_1 #xa0)) ((#xed) (<= u8_1 #x9f)) (else #t))) (kt (integer->char (logior (ash (logand u8_0 #x0f) 12) (ash (logand u8_1 #x3f) 6) (logand u8_2 #x3f))) 3) (kf)))) ((and (<= #xf0 u8_0 #xf4) (<= 4 avail)) (let ((u8_1 (bytevector-u8-ref bv (+ start 1))) (u8_2 (bytevector-u8-ref bv (+ start 2))) (u8_3 (bytevector-u8-ref bv (+ start 3)))) (if (and (= (logand u8_1 #xc0) #x80) (= (logand u8_2 #xc0) #x80) (= (logand u8_3 #xc0) #x80) (case u8_0 ((#xf0) (>= u8_1 #x90)) ((#xf4) (<= u8_1 #x8f)) (else #t))) (kt (integer->char (logior (ash (logand u8_0 #x07) 18) (ash (logand u8_1 #x3f) 12) (ash (logand u8_2 #x3f) 6) (logand u8_3 #x3f))) 4) (kf)))) (else (kf)))) (define (bad-utf8-len bv cur buffering first-byte) (define (ref n) (bytevector-u8-ref bv (+ cur n))) (cond ((< first-byte #x80) 0) ((<= #xc2 first-byte #xdf) (cond ((< buffering 2) 1) ((not (= (logand (ref 1) #xc0) #x80)) 1) (else 0))) ((= (logand first-byte #xf0) #xe0) (cond ((< buffering 2) 1) ((not (= (logand (ref 1) #xc0) #x80)) 1) ((and (eq? first-byte #xe0) (< (ref 1) #xa0)) 1) ((and (eq? first-byte #xed) (> (ref 1) #x9f)) 1) ((< buffering 3) 2) ((not (= (logand (ref 2) #xc0) #x80)) 2) (else 0))) ((<= #xf0 first-byte #xf4) (cond ((< buffering 2) 1) ((not (= (logand (ref 1) #xc0) #x80)) 1) ((and (eq? first-byte #xf0) (< (ref 1) #x90)) 1) ((and (eq? first-byte #xf4) (> (ref 1) #x8f)) 1) ((< buffering 3) 2) ((not (= (logand (ref 2) #xc0) #x80)) 2) ((< buffering 4) 3) ((not (= (logand (ref 3) #xc0) #x80)) 3) (else 0))) (else 1))) (define (peek-char-and-next-cur/utf8 port buf cur first-byte) (if (< first-byte #x80) (values (integer->char first-byte) buf (+ cur 1)) (call-with-values (lambda () (fill-input port (cond ((<= #xc2 first-byte #xdf) 2) ((= (logand first-byte #xf0) #xe0) 3) (else 4)))) (lambda (buf cur buffering) (let ((bv (port-buffer-bytevector buf))) (define (bad-utf8) (let ((len (bad-utf8-len bv cur buffering first-byte))) (when (zero? len) (error "internal error")) (if (eq? (port-conversion-strategy port) 'substitute) (values #\xFFFD buf (+ cur len)) (decoding-error "peek-char" port)))) (decode-utf8 bv cur buffering first-byte (lambda (char len) (values char buf (+ cur len))) bad-utf8)))))) (define (peek-char-and-next-cur/iso-8859-1 port buf cur first-byte) (values (integer->char first-byte) buf (+ cur 1))) (define (peek-char-and-next-cur/iconv port) (let lp ((prev-input-size 0)) (let ((input-size (1+ prev-input-size))) (call-with-values (lambda () (fill-input port input-size)) (lambda (buf cur buffered) (cond ((< buffered input-size) ;; Buffer failed to fill; EOF, possibly premature. (cond ((zero? prev-input-size) (values the-eof-object buf cur)) ((eq? (port-conversion-strategy port) 'substitute) (values #\xFFFD buf (+ cur prev-input-size))) (else (decoding-error "peek-char" port)))) ((port-decode-char port (port-buffer-bytevector buf) cur input-size) => (lambda (char) (values char buf (+ cur input-size)))) (else (lp input-size)))))))) (define (peek-char-and-next-cur port) (define (have-byte buf bv cur buffered) (let ((first-byte (bytevector-u8-ref bv cur))) (case (%port-encoding port) ((UTF-8) (peek-char-and-next-cur/utf8 port buf cur first-byte)) ((ISO-8859-1) (peek-char-and-next-cur/iso-8859-1 port buf cur first-byte)) (else (peek-char-and-next-cur/iconv port))))) (peek-bytes port 1 have-byte (lambda (buf bv cur buffered) (if (< 0 buffered) (have-byte buf bv cur buffered) (values the-eof-object buf cur))))) (define* (peek-char #:optional (port (current-input-port))) (define (slow-path) (call-with-values (lambda () (peek-char-and-next-cur port)) (lambda (char buf cur) char))) (define (fast-path buf bv cur buffered) (let ((u8 (bytevector-u8-ref bv cur)) (enc (%port-encoding port))) (case enc ((UTF-8) (decode-utf8 bv cur buffered u8 (lambda (char len) char) slow-path)) ((ISO-8859-1) (integer->char u8)) (else (slow-path))))) (peek-bytes port 1 fast-path (lambda (buf bv cur buffered) (slow-path)))) (define-inlinable (advance-port-position! pos char) ;; FIXME: this cond is a speed hack; really we should just compile ;; `case' better. (cond ;; FIXME: char>? et al should compile well. ((<= (char->integer #\space) (char->integer char)) (set-port-position-column! pos (1+ (port-position-column pos)))) (else (case char ((#\alarm) #t) ; No change. ((#\backspace) (let ((col (port-position-column pos))) (when (> col 0) (set-port-position-column! pos (1- col))))) ((#\newline) (set-port-position-line! pos (1+ (port-position-line pos))) (set-port-position-column! pos 0)) ((#\return) (set-port-position-column! pos 0)) ((#\tab) (let ((col (port-position-column pos))) (set-port-position-column! pos (- (+ col 8) (remainder col 8))))) (else (set-port-position-column! pos (1+ (port-position-column pos)))))))) (define* (read-char #:optional (port (current-input-port))) (define (finish buf char) (advance-port-position! (port-buffer-position buf) char) char) (define (slow-path) (call-with-values (lambda () (peek-char-and-next-cur port)) (lambda (char buf cur) (set-port-buffer-cur! buf cur) (if (eq? char the-eof-object) (begin (set-port-buffer-has-eof?! buf #f) char) (finish buf char))))) (define (fast-path buf bv cur buffered) (let ((u8 (bytevector-u8-ref bv cur)) (enc (%port-encoding port))) (case enc ((UTF-8) (decode-utf8 bv cur buffered u8 (lambda (char len) (set-port-buffer-cur! buf (+ cur len)) (finish buf char)) slow-path)) ((ISO-8859-1) (set-port-buffer-cur! buf (+ cur 1)) (finish buf (integer->char u8))) (else (slow-path))))) (peek-bytes port 1 fast-path (lambda (buf bv cur buffered) (slow-path)))) (define-inlinable (port-fold-chars/iso-8859-1 port proc seed) (let* ((buf (port-read-buffer port)) (cur (port-buffer-cur buf))) (let fold-buffer ((buf buf) (cur cur) (seed seed)) (let ((bv (port-buffer-bytevector buf)) (end (port-buffer-end buf))) (let fold-chars ((cur cur) (seed seed)) (cond ((= end cur) (call-with-values (lambda () (fill-input port)) (lambda (buf cur buffered) (if (zero? buffered) (call-with-values (lambda () (proc the-eof-object seed)) (lambda (seed done?) (if done? seed (fold-buffer buf cur seed)))) (fold-buffer buf cur seed))))) (else (let ((ch (integer->char (bytevector-u8-ref bv cur))) (cur (1+ cur))) (set-port-buffer-cur! buf cur) (advance-port-position! (port-buffer-position buf) ch) (call-with-values (lambda () (proc ch seed)) (lambda (seed done?) (if done? seed (fold-chars cur seed)))))))))))) (define-inlinable (port-fold-chars port proc seed) (case (%port-encoding port) ((ISO-8859-1) (port-fold-chars/iso-8859-1 port proc seed)) (else (let lp ((seed seed)) (let ((ch (read-char port))) (call-with-values (lambda () (proc ch seed)) (lambda (seed done?) (if done? seed (lp seed))))))))) (define* (read-delimited delims #:optional (port (current-input-port)) (handle-delim 'trim)) ;; Currently this function conses characters into a list, then uses ;; reverse-list->string. It wastes 2 words per character but it still ;; seems to be the fastest thing at the moment. (define (finish delim chars) (define (->string chars) (if (and (null? chars) (not (char? delim))) the-eof-object (reverse-list->string chars))) (case handle-delim ((trim) (->string chars)) ((split) (cons (->string chars) delim)) ((concat) (->string (if (char? delim) (cons delim chars) chars))) ((peek) (when (char? delim) (unread-char delim port)) (->string chars)) (else (error "unexpected handle-delim value: " handle-delim)))) (define-syntax-rule (make-folder delimiter?) (lambda (char chars) (if (or (not (char? char)) (delimiter? char)) (values (finish char chars) #t) (values (cons char chars) #f)))) (define-syntax-rule (specialized-fold delimiter?) (port-fold-chars port (make-folder delimiter?) '())) (case (string-length delims) ((0) (specialized-fold (lambda (char) #f))) ((1) (let ((delim (string-ref delims 0))) (specialized-fold (lambda (char) (eqv? char delim))))) (else => (lambda (ndelims) (specialized-fold (lambda (char) (let lp ((i 0)) (and (< i ndelims) (or (eqv? char (string-ref delims i)) (lp (1+ i))))))))))) (define* (read-line #:optional (port (current-input-port)) (handle-delim 'trim)) (read-delimited "\n" port handle-delim)) (define* (%read-line port) (read-line port 'split)) (define* (put-string port str #:optional (start 0) (count (- (string-length str) start))) (let* ((aux (port-auxiliary-write-buffer port)) (pos (port-buffer-position aux)) (line (port-position-line pos))) (set-port-buffer-cur! aux 0) (port-clear-stream-start-for-bom-write port aux) (let lp ((encoded 0)) (when (< encoded count) (let ((encoded (+ encoded (port-encode-chars port aux str (+ start encoded) (- count encoded))))) (let ((end (port-buffer-end aux))) (set-port-buffer-end! aux 0) (put-bytevector port (port-buffer-bytevector aux) 0 end) (lp encoded))))) (when (and (not (eqv? line (port-position-line pos))) (port-line-buffered? port)) (flush-output port)))) (define* (put-char port char) (let ((aux (port-auxiliary-write-buffer port))) (set-port-buffer-cur! aux 0) (port-clear-stream-start-for-bom-write port aux) (port-encode-char port aux char) (let ((end (port-buffer-end aux))) (set-port-buffer-end! aux 0) (put-bytevector port (port-buffer-bytevector aux) 0 end)) (when (and (eqv? char #\newline) (port-line-buffered? port)) (flush-output port)))) (define accept (let ((%accept (@ (guile) accept))) (lambda* (port #:optional (flags 0)) (let lp () (or (%accept port flags) (begin (wait-for-readable port) (lp))))))) (define connect (let ((%connect (@ (guile) connect))) (lambda (port sockaddr . args) (unless (apply %connect port sockaddr args) ;; Clownshoes semantics; see connect(2). (wait-for-writable port) (let ((err (getsockopt port SOL_SOCKET SO_ERROR))) (unless (zero? err) (scm-error 'system-error "connect" "~A" (list (strerror err)) #f))))))) (define saved-port-bindings #f) (define port-bindings '(((guile) read-char peek-char force-output close-port accept connect) ((ice-9 binary-ports) get-u8 lookahead-u8 get-bytevector-n get-bytevector-n! get-bytevector-some get-bytevector-some! put-u8 put-bytevector) ((ice-9 textual-ports) put-char put-string) ((ice-9 rdelim) %read-line read-line read-delimited))) (define (install-suspendable-ports!) (unless saved-port-bindings (set! saved-port-bindings (make-hash-table)) (let ((suspendable-ports (resolve-module '(ice-9 suspendable-ports)))) (for-each (match-lambda ((mod . syms) (let ((mod (resolve-module mod))) (for-each (lambda (sym) (hashq-set! saved-port-bindings sym (module-ref mod sym)) (module-set! mod sym (module-ref suspendable-ports sym))) syms)))) port-bindings)))) (define (uninstall-suspendable-ports!) (when saved-port-bindings (for-each (match-lambda ((mod . syms) (let ((mod (resolve-module mod))) (for-each (lambda (sym) (let ((saved (hashq-ref saved-port-bindings sym))) (module-set! mod sym saved))) syms)))) port-bindings) (set! saved-port-bindings #f)))