summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Wingo <wingo@pobox.com>2016-05-13 09:22:36 +0200
committerAndy Wingo <wingo@pobox.com>2016-05-13 09:22:36 +0200
commit704c42870d63eb469c52375575d1a8e3f8eadce3 (patch)
tree03c0531f3efad49f702ec35438137ab32bbd7be6
parentd1bb400c3f378f28a72eb9e39178d9fed1d44b2d (diff)
downloadguile-704c42870d63eb469c52375575d1a8e3f8eadce3.tar.gz
Add (ice-9 sports) module
* module/ice-9/sports.scm: New module. * module/Makefile.am (SOURCES): Add new module.
-rw-r--r--module/Makefile.am1
-rw-r--r--module/ice-9/sports.scm412
2 files changed, 413 insertions, 0 deletions
diff --git a/module/Makefile.am b/module/Makefile.am
index 71b265ae4..7f8284e18 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -106,6 +106,7 @@ SOURCES = \
ice-9/serialize.scm \
ice-9/session.scm \
ice-9/slib.scm \
+ ice-9/sports.scm \
ice-9/stack-catch.scm \
ice-9/streams.scm \
ice-9/string-fun.scm \
diff --git a/module/ice-9/sports.scm b/module/ice-9/sports.scm
new file mode 100644
index 000000000..55f507866
--- /dev/null
+++ b/module/ice-9/sports.scm
@@ -0,0 +1,412 @@
+;;; Ports, implemented in Scheme
+;;; Copyright (C) 2016 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 sports)
+ #:use-module (rnrs bytevectors)
+ #:use-module (ice-9 ports internal)
+ #:replace (peek-char
+ read-char)
+ #:export (lookahead-u8
+ get-u8))
+
+(define (write-bytes port src start count)
+ (let ((written ((port-write port) port src start count)))
+ (unless (<= 0 written count)
+ (error "bad return from port write function" written))
+ (when (< written count)
+ (write-bytes port src (+ start written) (- count written)))))
+
+(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 (read-bytes port dst start count)
+ (let ((read ((port-read port) port dst start count)))
+ (unless (<= 0 read count)
+ (error "bad return from port read function" read))
+ read))
+
+(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 buffered)
+ (and (<= (bytevector-length bom) buffered)
+ (let ((bv (port-buffer-bytevector buf))
+ (cur (port-buffer-cur 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))
+ (clear-stream-start-for-bom-read port 'text)
+ (let* ((buf (port-read-buffer port))
+ (cur (port-buffer-cur buf))
+ (buffered (- (port-buffer-end buf) cur)))
+ (cond
+ ((or (<= minimum-buffering buffered) (port-buffer-has-eof? buf))
+ (values buf 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 buffered))
+ (else
+ (let ((buffered (+ buffered read)))
+ (set-port-buffer-end! buf buffered)
+ (if (< buffered minimum-buffering)
+ (lp buffered)
+ (values buf buffered)))))))))))))))
+
+(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 buffered)
+ (kslow buf (port-buffer-bytevector buf) (port-buffer-cur buf)
+ 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 (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-len/utf8 port first-byte)
+ (define (bad-utf8 len)
+ (if (eq? (port-conversion-strategy port) 'substitute)
+ (values #\? len)
+ (decoding-error "peek-char" port)))
+ (if (< first-byte #x80)
+ (values (integer->char first-byte) 1)
+ (call-with-values (lambda ()
+ (fill-input port
+ (cond
+ ((<= #xc2 first-byte #xdf) 2)
+ ((= (logand first-byte #xf0) #xe0) 3)
+ (else 4))))
+ (lambda (buf buffering)
+ (let* ((bv (port-buffer-bytevector buf))
+ (cur (port-buffer-cur 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 #\? len)
+ (decoding-error "peek-char" port))))
+ (decode-utf8 bv cur buffering first-byte values bad-utf8))))))
+
+(define (peek-char-and-len/iso-8859-1 port first-byte)
+ (values (integer->char first-byte) 1))
+
+(define (peek-char-and-len/iconv port first-byte)
+ (let lp ((prev-input-size 0))
+ (let ((input-size (1+ prev-input-size)))
+ (call-with-values (lambda () (fill-input port input-size))
+ (lambda (buf buffered)
+ (cond
+ ((< buffered input-size)
+ ;; Buffer failed to fill; EOF, possibly premature.
+ (cond
+ ((zero? prev-input-size)
+ (values the-eof-object 0))
+ ((eq? (port-conversion-strategy port) 'substitute)
+ (values #\? prev-input-size))
+ (else
+ (decoding-error "peek-char" port))))
+ ((port-decode-char port (port-buffer-bytevector buf)
+ (port-buffer-cur buf) input-size)
+ => (lambda (char)
+ (values char input-size)))
+ (else
+ (lp input-size))))))))
+
+(define (peek-char-and-len port)
+ (let ((first-byte (peek-byte port)))
+ (if (not first-byte)
+ (values the-eof-object 0)
+ (case (%port-encoding port)
+ ((UTF-8)
+ (peek-char-and-len/utf8 port first-byte))
+ ((ISO-8859-1)
+ (peek-char-and-len/iso-8859-1 port first-byte))
+ (else
+ (peek-char-and-len/iconv port first-byte))))))
+
+(define* (peek-char #:optional (port (current-input-port)))
+ (define (slow-path)
+ (call-with-values (lambda () (peek-char-and-len port))
+ (lambda (char len)
+ 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* (read-char #:optional (port (current-input-port)))
+ (define (update-position! char)
+ (case char
+ ((#\alarm) #t) ; No change.
+ ((#\backspace)
+ (let ((col (port-column port)))
+ (when (> col 0)
+ (set-port-column! port (1- col)))))
+ ((#\newline)
+ (set-port-line! port (1+ (port-line port)))
+ (set-port-column! port 0))
+ ((#\return)
+ (set-port-column! port 0))
+ ((#\tab)
+ (let ((col (port-column port)))
+ (set-port-column! port (- (+ col 8) (remainder col 8)))))
+ (else
+ (set-port-column! port (1+ (port-column port)))))
+ char)
+ (define (slow-path)
+ (call-with-values (lambda () (peek-char-and-len port))
+ (lambda (char len)
+ (let ((buf (port-read-buffer port)))
+ (set-port-buffer-cur! buf (+ (port-buffer-cur buf) len))
+ (if (eq? char the-eof-object)
+ (begin
+ (set-port-buffer-has-eof?! buf #f)
+ char)
+ (update-position! 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))
+ (update-position! char))
+ slow-path))
+ ((ISO-8859-1)
+ (set-port-buffer-cur! buf (+ cur 1))
+ (update-position! (integer->char u8)))
+ (else (slow-path)))))
+ (peek-bytes port 1 fast-path
+ (lambda (buf bv cur buffered) (slow-path))))