123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789 |
- ;;; 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
- ;;; <http://www.gnu.org/licenses/>.
- ;;; 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)))
|