wingo pushed a commit to branch wip-custom-ports in repository guile. commit 2338463791421a0b0ca79123c1191bb04c4c123c Author: Andy Wingo <wi...@pobox.com> AuthorDate: Sun May 28 14:20:34 2023 +0200
Implement R6RS custom textual ports * module/ice-9/textual-ports.scm (custom-textual-port-read+flush-input): (custom-textual-port-write): (custom-textual-port-seek): (custom-textual-port-close): (custom-textual-port-random-access?): (make-custom-textual-input-port): (make-custom-textual-output-port): (make-custom-textual-input/output-port): New procedures. --- module/ice-9/textual-ports.scm | 167 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 2 deletions(-) diff --git a/module/ice-9/textual-ports.scm b/module/ice-9/textual-ports.scm index ba30a8b1f..03c22327f 100644 --- a/module/ice-9/textual-ports.scm +++ b/module/ice-9/textual-ports.scm @@ -1,6 +1,6 @@ ;;;; textual-ports.scm --- Textual I/O on ports -;;;; Copyright (C) 2016 Free Software Foundation, Inc. +;;;; Copyright (C) 2016, 2023 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 @@ -23,7 +23,10 @@ (define-module (ice-9 textual-ports) #:use-module (ice-9 ports internal) #:use-module (ice-9 binary-ports) + #:use-module (ice-9 custom-ports) + #:use-module (ice-9 match) #:use-module (ice-9 rdelim) + #:use-module (rnrs bytevectors) #:re-export (get-string-n! put-char put-string) @@ -33,7 +36,10 @@ lookahead-char get-string-n get-string-all - get-line)) + get-line + make-custom-textual-input-port + make-custom-textual-output-port + make-custom-textual-input/output-port)) (define (get-char port) (read-char port)) @@ -68,3 +74,160 @@ the characters read." (cond ((eof-object? rv) rv) ((= rv count) s) (else (substring/shared s 0 rv))))) + +(define (type-error proc expecting val) + (scm-error 'wrong-type-arg proc "Wrong type (expecting `~S'): ~S" + (list expecting val) (list val))) + +(define (custom-textual-port-read+flush-input read) + (unless (procedure? read) + (type-error "custom-textual-port-read" "procedure" read)) + (define-values (transcoder get-bytes) (open-bytevector-output-port)) + (define buffer #f) + (define buffer-pos 0) + (define (%read port bv start count) + (unless (and buffer (< buffer-pos (bytevector-length buffer))) + (let* ((str (make-string (max (port-read-buffering port) 1))) + (chars (read str 0 (string-length str)))) + (unless (and (exact-integer? chars) (<= 0 chars (string-length str))) + (scm-error 'out-of-range "custom-textual-port-read" + "Value out of range: ~S" (list chars) (list chars))) + (cond + ((zero? chars) 0) + (else + (unless (eq? (port-encoding port) (port-encoding transcoder)) + (set-port-encoding! transcoder (port-encoding port))) + (unless (eq? (port-conversion-strategy port) + (port-conversion-strategy transcoder)) + (set-port-conversion-strategy! transcoder + (port-conversion-strategy port))) + (put-string transcoder str 0 chars) + (set! buffer (get-bytes)) + (set! buffer-pos 0))))) + + (let ((to-copy (min count (- (bytevector-length buffer) buffer-pos)))) + (bytevector-copy! buffer buffer-pos bv start to-copy) + (if (= (bytevector-length buffer) (+ buffer-pos to-copy)) + (set! buffer #f) + (set! buffer-pos (+ buffer-pos to-copy))) + to-copy)) + (define (%flush-input) + (get-bytes) + (set! buffer #f)) + (values %read %flush-input)) + +(define (subbytevector bv start count) + (if (and (zero? start) (= count (bytevector-length bv))) + bv + (let ((sub (make-bytevector count))) + (bytevector-copy! bv start sub 0 count) + sub))) + +(define (custom-textual-port-write write) + (unless (procedure? write) + (type-error "custom-textual-port-write" "procedure" write)) + (lambda (port bv start count) + (let* ((bytes (subbytevector bv start count)) + (str (call-with-input-bytevector + bytes + (lambda (bport) + (set-port-encoding! bport (port-encoding port)) + (set-port-conversion-strategy! + bport + (port-conversion-strategy port)) + (get-string-all port)))) + (len (string-length str))) + (let lp ((written 0)) + (cond + ((= written len) count) + (else + (let ((to-write (- len written))) + (let ((res (write str written to-write))) + (unless (and (exact-integer? res) (<= 0 res to-write)) + (scm-error 'out-of-range "custom-textual-port-write" + "Value out of range: ~S" (list res) (list res))) + (lp (+ written res)))))))))) + +(define (custom-textual-port-seek get-position set-position! flush-input) + (when get-position + (unless (procedure? get-position) + (type-error "custom-textual-port-seek" "procedure" get-position))) + (when set-position! + (unless (procedure? set-position!) + (type-error "custom-textual-port-seek" "procedure" set-position!))) + + (define (seek port offset whence) + (cond + ((eqv? whence SEEK_CUR) + (unless get-position + (type-error "custom-textual-port-seek" + "R6RS custom textual port with `port-position` support" + port)) + (if (zero? offset) + (get-position) + (seek port (+ (get-position) offset) SEEK_SET))) + ((eqv? whence SEEK_SET) + (unless set-position! + (type-error "custom-textual-port-seek" + "Seekable R6RS custom textual port" + port)) + (flush-input) + (set-position! offset) + ;; Assume setting the position succeeds. + offset) + ((eqv? whence SEEK_END) + (error "R6RS custom textual ports do not support `SEEK_END'")))) + seek) + +(define (custom-textual-port-close close) + (match close + (#f (lambda (port) #t)) + ((? procedure?) (lambda (port) (close))) + (_ (type-error "custom-textual-port-close" "procedure" close)))) + +(define (custom-textual-port-random-access? set-position!) + (if set-position! + (lambda (port) #t) + (lambda (port) #f))) + +(define (make-custom-textual-input-port id read get-position set-position! + close) + (unless (string? id) + (type-error "make-custom-textual-input-port" "string" id)) + (define-values (%read %flush-input) + (custom-textual-port-read+flush-input read)) + (make-custom-port #:id id + #:read %read + #:seek (custom-textual-port-seek get-position set-position! + %flush-input) + #:close (custom-textual-port-close close) + #:random-access? + (custom-textual-port-random-access? set-position!))) + +(define (make-custom-textual-output-port id write get-position set-position! + close) + (unless (string? id) + (type-error "make-custom-textual-output-port" "string" id)) + (define (flush-input) #t) + (make-custom-port #:id id + #:write (custom-textual-port-write write) + #:seek (custom-textual-port-seek get-position set-position! + flush-input) + #:close (custom-textual-port-close close) + #:random-access? + (custom-textual-port-random-access? set-position!))) + +(define (make-custom-textual-input/output-port id read write get-position + set-position! close) + (unless (string? id) + (type-error "make-custom-textual-input/output-port" "string" id)) + (define-values (%read %flush-input) + (custom-textual-port-read+flush-input read)) + (make-custom-port #:id id + #:read %read + #:write (custom-textual-port-write write) + #:seek (custom-textual-port-seek get-position set-position! + %flush-input) + #:close (custom-textual-port-close close) + #:random-access? + (custom-textual-port-random-access? set-position!)))