mirror of
https://github.com/ashinn/chibi-scheme.git
synced 2025-05-19 21:59:17 +02:00
They can be close()d explicitly with close-file-descriptor, and will close() on gc, but only explicitly closing the last port on them will close the fileno. Notably needed for network sockets where we open separate input and output ports on the same socket.
323 lines
11 KiB
Scheme
323 lines
11 KiB
Scheme
;; io.scm -- various input/output utilities
|
|
;; Copyright (c) 2010-2012 Alex Shinn. All rights reserved.
|
|
;; BSD-style license: http://synthcode.com/license.txt
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; utilities
|
|
|
|
(define eof
|
|
(call-with-input-string " "
|
|
(lambda (in) (read-char in) (read-char in))))
|
|
|
|
(define (string-copy! dst start src from to)
|
|
(do ((i from (+ i 1)) (j start (+ j 1)))
|
|
((>= i to))
|
|
(string-set! dst j (string-ref src i))))
|
|
|
|
(define (utf8->string vec . o)
|
|
(if (pair? o)
|
|
(let ((start (car o))
|
|
(end (if (pair? (cdr o)) (cadr o) (bytevector-length vec))))
|
|
(utf8->string (subbytes vec start end)))
|
|
(string-copy (utf8->string! vec))))
|
|
|
|
(define (string->utf8 str . o)
|
|
(if (pair? o)
|
|
(let ((start (car o))
|
|
(end (if (pair? (cdr o)) (cadr o) (string-length str))))
|
|
(string->utf8 (substring str start end)))
|
|
(%string->utf8 str)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; reading and writing
|
|
|
|
;; Display \var{str} to the given output port, defaulting to
|
|
;; \scheme{(current-output-port)}, followed by a newline.
|
|
|
|
(define (write-line str . o)
|
|
(let ((out (if (pair? o) (car o) (current-output-port))))
|
|
(display str out)
|
|
(newline out)))
|
|
|
|
;;> \procedure{(write-string str [out [start [end]]])}
|
|
|
|
;;> Writes the characters from \var{start} to \var{end} of string
|
|
;;> \var{str} to output port \var{out}, where \var{start} defaults
|
|
;;> to 0 and \var{end} defaults to \scheme{(string-length \var{str})}.
|
|
|
|
(define (write-string str . o)
|
|
(let ((out (if (pair? o) (car o) (current-output-port)))
|
|
(o (if (pair? o) (cdr o) o)))
|
|
(if (pair? o)
|
|
(let ((start (car o))
|
|
(end (if (pair? (cdr o)) (cadr o) (string-length str))))
|
|
(cond-expand
|
|
(string-streams
|
|
(if (zero? start)
|
|
(%write-string str end out)
|
|
(display (substring str start end) out)))
|
|
(else
|
|
(display (substring str start end) out))))
|
|
(display str out))))
|
|
|
|
;;> \procedure{(read-line [in [n]])}
|
|
|
|
;;> Read a line from the input port \var{in}, defaulting to
|
|
;;> \scheme{(current-input-port)}, and return the result as
|
|
;;> a string not including the newline. Reads at most \var{n}
|
|
;;> characters, defaulting to 8192.
|
|
|
|
(cond-expand
|
|
((not string-streams)
|
|
(define (%read-line n in)
|
|
(let ((out (open-output-string)))
|
|
(let lp ()
|
|
(let ((ch (read-char in)))
|
|
(cond
|
|
((eof-object? ch)
|
|
(let ((res (get-output-string out)))
|
|
(and (not (equal? res "")) res)))
|
|
(else
|
|
(write-char ch out)
|
|
(cond
|
|
((eqv? ch #\newline)
|
|
(get-output-string out))
|
|
((eqv? ch #\return)
|
|
(if (eqv? #\newline (peek-char in))
|
|
(read-char in))
|
|
(get-output-string out))
|
|
(else
|
|
(lp)))))))))))
|
|
|
|
(define (read-line . o)
|
|
(let ((in (if (pair? o) (car o) (current-input-port)))
|
|
(n (if (and (pair? o) (pair? (cdr o))) (car (cdr o)) 8192)))
|
|
(let ((res (%read-line n in)))
|
|
(cond-expand
|
|
(string-streams
|
|
(port-line-set! in (+ 1 (port-line in)))))
|
|
(if (not res)
|
|
eof
|
|
(let ((len (string-length res)))
|
|
(cond
|
|
((and (> len 0) (eqv? #\newline (string-ref res (- len 1))))
|
|
(if (and (> len 1) (eqv? #\return (string-ref res (- len 2))))
|
|
(substring res 0 (- len 2))
|
|
(substring res 0 (- len 1))))
|
|
((and (> len 0) (eqv? #\return (string-ref res (- len 1))))
|
|
(substring res 0 (- len 1)))
|
|
(else
|
|
res)))))))
|
|
|
|
;;> \procedure{(read-string n [in])}
|
|
|
|
;;> Reads \var{n} characters from input-port \var{in},
|
|
;;> defaulting to \scheme{(current-input-port)}, and
|
|
;;> returns the result as a string. Returns \scheme{""}
|
|
;;> if \var{n} is zero. May return a string with fewer
|
|
;;> than \var{n} characters if the end of file is reached,
|
|
;;> or the eof-object if no characters are available.
|
|
|
|
(cond-expand
|
|
((not string-streams)
|
|
(define (%read-string n in)
|
|
(let ((out (open-output-string)))
|
|
(let lp ((i 0))
|
|
(cond ((or (= i n) (eof-object? (peek-char in)))
|
|
(list i (get-output-string out)))
|
|
(else (write-char (read-char in) out) (lp (+ i 1)))))))))
|
|
|
|
(define (read-string n . o)
|
|
(if (zero? n)
|
|
""
|
|
(let ((in (if (pair? o) (car o) (current-input-port))))
|
|
(let ((res (%read-string n in)))
|
|
(cond
|
|
((if (pair? res) (= 0 (car res)) #t)
|
|
eof)
|
|
(else
|
|
(port-line-set! in (+ (string-count-chars #\newline (cadr res) 0)
|
|
(port-line in)))
|
|
(cadr res)))))))
|
|
|
|
;;> \procedure{(read-string! str n [in])}
|
|
|
|
;;> Reads \var{n} characters from port \var{in}, which
|
|
;;> defaults to \scheme{(current-input-port)}, and writes
|
|
;;> them into the string \var{str} starting at index 0.
|
|
;;> Returns the number of characters read.
|
|
;;> An error is signalled if the length of \var{str} is smaller
|
|
;;> than \var{n}.
|
|
|
|
(cond-expand
|
|
((not string-streams)
|
|
(define (%read-string! str n in)
|
|
(let lp ((i 0))
|
|
(cond ((or (= i n) (eof-object? (peek-char in))) i)
|
|
(else (string-set! str i (read-char in)) (lp (+ i 1))))))))
|
|
|
|
(define (read-string! str n . o)
|
|
(if (> n (string-length str))
|
|
(error "string to small to read chars" str n))
|
|
(let* ((in (if (pair? o) (car o) (current-input-port)))
|
|
(res (%read-string! str n in)))
|
|
(port-line-set! in (+ (string-count-chars #\newline str 0 n) (port-line in)))
|
|
res))
|
|
|
|
;;> Sends the entire contents of a file or input port to an output port.
|
|
|
|
(define (send-file fd-port-or-filename . o)
|
|
(let* ((in (if (string? fd-port-or-filename)
|
|
(open-input-file fd-port-or-filename)
|
|
fd-port-or-filename))
|
|
(out (if (pair? o) (car o) (current-output-port)))
|
|
(fd (if (port? in) (port-fileno in) in))
|
|
(sock (if (port? out) (port-fileno out) out)))
|
|
(if (and fd sock (is-a-socket? sock))
|
|
(let lp ((start 0))
|
|
(let ((res (%send-file fd sock start)))
|
|
(cond
|
|
((not res) (lp start))
|
|
((not (zero? res)) (lp (+ start res))))))
|
|
(let lp ()
|
|
(let ((str (read-string 8192 in)))
|
|
(cond ((not (eof-object? str))
|
|
(display str out)
|
|
(lp))))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; higher order port operations
|
|
|
|
;;> The fundamental port iterator.
|
|
|
|
(define (port-fold kons knil . o)
|
|
(let ((read (if (pair? o) (car o) read))
|
|
(in (if (and (pair? o) (pair? (cdr o)))
|
|
(car (cdr o))
|
|
(current-input-port))))
|
|
(let lp ((acc knil))
|
|
(let ((x (read in)))
|
|
(if (eof-object? x) acc (lp (kons x acc)))))))
|
|
|
|
(define (port-fold-right kons knil . o)
|
|
(let ((read (if (pair? o) (car o) read))
|
|
(in (if (and (pair? o) (pair? (cdr o)))
|
|
(car (cdr o))
|
|
(current-input-port))))
|
|
(let lp ()
|
|
(let ((x (read in)))
|
|
(if (eof-object? x) knil (kons x (lp)))))))
|
|
|
|
(define (port-map fn . o)
|
|
(reverse (apply port-fold (lambda (x ls) (cons (fn x) ls)) '() o)))
|
|
|
|
(define (port->list read in)
|
|
(port-map (lambda (x) x) read in))
|
|
|
|
(define (port->sexp-list in)
|
|
(port->list read in))
|
|
|
|
(define (port->string-list in)
|
|
(port->list read-line in))
|
|
|
|
(define (port->string in)
|
|
(string-concatenate (port->list (lambda (in) (read-string 1024 in)) in)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; custom port utilities
|
|
|
|
(define (make-custom-input-port read . o)
|
|
(let ((seek (and (pair? o) (car o)))
|
|
(close (and (pair? o) (pair? (cdr o)) (car (cdr o)))))
|
|
(%make-custom-input-port read seek close)))
|
|
|
|
(define (make-custom-output-port write . o)
|
|
(let ((seek (and (pair? o) (car o)))
|
|
(close (and (pair? o) (pair? (cdr o)) (car (cdr o)))))
|
|
(%make-custom-output-port write seek close)))
|
|
|
|
(define (make-null-output-port)
|
|
(make-custom-output-port (lambda (str start end) 0)))
|
|
|
|
(define (make-broadcast-port . ports)
|
|
(make-custom-output-port
|
|
(lambda (str start end)
|
|
(let ((str (if (zero? start) str (substring str start)))
|
|
(n (- end start)))
|
|
(for-each (lambda (p) (%write-string str n p)) ports)
|
|
n))))
|
|
|
|
(define (make-filtered-output-port filter out)
|
|
(make-custom-output-port
|
|
(lambda (str start end)
|
|
(let* ((len (string-length str))
|
|
(s1 (if (and (zero? start) (= end len)) str (substring str start end)))
|
|
(s2 (filter s1)))
|
|
(if (string? s2)
|
|
(%write-string s2 (string-length s2) out))))))
|
|
|
|
(define (make-concatenated-port . ports)
|
|
(make-custom-input-port
|
|
(lambda (str start end)
|
|
(if (null? ports)
|
|
0
|
|
(let ((str (if (zero? start) str (substring str start)))
|
|
(n (- end start)))
|
|
(let lp ((i (read-string! str n (car ports))))
|
|
(cond
|
|
((>= i n)
|
|
i)
|
|
(else
|
|
(set! ports (cdr ports))
|
|
(cond
|
|
((null? ports)
|
|
i)
|
|
(else
|
|
(let* ((s (read-string (- n i) (car ports)))
|
|
(len (if (string? s) (string-length s) 0)))
|
|
(if (and (string? str) (> len 0))
|
|
(string-copy! str i s 0 len))
|
|
(lp (+ i len)))))))))))))
|
|
|
|
(define (make-null-input-port)
|
|
(make-concatenated-port))
|
|
|
|
(define (make-generated-input-port generator)
|
|
(let ((buf "")
|
|
(len 0)
|
|
(offset 0))
|
|
(make-custom-input-port
|
|
(lambda (str start end)
|
|
(let ((n (- end start)))
|
|
(cond
|
|
((>= (- len offset) n)
|
|
(string-copy! str start buf offset (+ offset n))
|
|
(set! offset (+ offset n))
|
|
n)
|
|
(else
|
|
(string-copy! str start buf offset len)
|
|
(let lp ((i (+ start (- len offset))))
|
|
(set! buf (generator))
|
|
(cond
|
|
((not (string? buf))
|
|
(set! buf "")
|
|
(set! len 0)
|
|
(set! offset 0)
|
|
(- i start))
|
|
(else
|
|
(set! len (string-length buf))
|
|
(set! offset 0)
|
|
(cond
|
|
((>= (- len offset) (- n i))
|
|
(string-copy! str i buf offset (+ offset (- n i)))
|
|
(set! offset (+ offset (- n i)))
|
|
n)
|
|
(else
|
|
(string-copy! str i buf offset len)
|
|
(lp (+ i (- len offset)))))))))))))))
|
|
|
|
(define (make-filtered-input-port filter in)
|
|
(make-generated-input-port
|
|
(lambda ()
|
|
(let ((res (read-string 1024 in)))
|
|
(if (string? res) (filter res) res)))))
|