mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 03:40:34 +02:00
This reverts commit 0f983e3db0
.
After discussing with Mike we are going to punt the read-line changes
for now. Open the port in O_TEXT mode if you want to chomp the CR in
CFLF sequences.
788 lines
30 KiB
Scheme
788 lines
30 KiB
Scheme
;;; 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
|
|
transfer-size start buffered)
|
|
(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)))
|