342 lines
10 KiB
Scheme
342 lines
10 KiB
Scheme
; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING.
|
|
|
|
; Additional port types
|
|
|
|
;----------------
|
|
; Ports which keep track of the current row and column.
|
|
;
|
|
; When the row or column data is requested we need to process the characters
|
|
; between the port's current index and the index at the time of the previous
|
|
; check.
|
|
;
|
|
; When a buffer operation is requested we need to process any remaining
|
|
; characters in the old buffer. If the operation is a block read or write
|
|
; we also need to process whatever is read or written.
|
|
;
|
|
; port: the tracking port - needed for its buffer and indicies
|
|
; sub-port: port being tracked
|
|
; index: the index of the next character to be processed
|
|
; row, column: position of the character at BUFFER[INDEX - 1]
|
|
|
|
(define-record-type port-location :port-location
|
|
(really-make-port-location sub-port index row column)
|
|
port-location?
|
|
(port port-location-port set-port-location-port!) ; setter for circularity
|
|
(sub-port port-location-sub-port)
|
|
(index port-location-index set-port-location-index!)
|
|
(row port-location-row set-port-location-row!)
|
|
(column port-location-column set-port-location-column!))
|
|
|
|
(define (make-port-location sub-port)
|
|
(really-make-port-location sub-port (port-index sub-port) 0 0))
|
|
|
|
; Update the data and return what you get.
|
|
|
|
(define (current-row port)
|
|
(let ((data (port-data port)))
|
|
(if (port-location? data)
|
|
(begin
|
|
(obtain-port-lock port)
|
|
(update-row-and-column! data)
|
|
(let ((row (port-location-row data)))
|
|
(release-port-lock port)
|
|
row))
|
|
#f)))
|
|
|
|
(define (current-column port)
|
|
(let ((data (port-data port)))
|
|
(if (port-location? data)
|
|
(begin
|
|
(obtain-port-lock port)
|
|
(update-row-and-column! data)
|
|
(let ((column (port-location-column data)))
|
|
(release-port-lock port)
|
|
column))
|
|
#f)))
|
|
|
|
; Bring LOCATION up to date.
|
|
|
|
(define (update-row-and-column! location)
|
|
(let ((at (port-index (port-location-port location)))
|
|
(checked-to (port-location-index location))
|
|
(buffer (port-buffer (port-location-port location))))
|
|
(if (< checked-to at)
|
|
(begin
|
|
(update-row-and-column-from-bytes! buffer checked-to at location)
|
|
(set-port-location-index! location at)))))
|
|
|
|
; Two nearly identical procedures to deal with code-vectors and strings.
|
|
|
|
(define (update-row-and-column-from-bytes! code-vec start end location)
|
|
(let loop ((i start)
|
|
(row (port-location-row location))
|
|
(column (port-location-column location)))
|
|
(cond ((= i end)
|
|
(set-port-location-row! location row)
|
|
(set-port-location-column! location column))
|
|
((= (char->ascii #\newline) (code-vector-ref code-vec i))
|
|
(loop (+ i 1) (+ row 1) 0))
|
|
(else
|
|
(loop (+ i 1) row (+ column 1))))))
|
|
|
|
(define (update-row-and-column-from-chars! string start count location)
|
|
(let loop ((i start)
|
|
(row (port-location-row location))
|
|
(column (port-location-column location)))
|
|
(cond ((= i (+ start count))
|
|
(set-port-location-row! location row)
|
|
(set-port-location-column! location column))
|
|
((char=? #\newline (string-ref string i))
|
|
(loop (+ i 1) (+ row 1) 0))
|
|
(else
|
|
(loop (+ i 1) row (+ column 1))))))
|
|
|
|
;----------------
|
|
; Input ports that keep track of the current row and column.
|
|
|
|
(define tracking-input-port-handler
|
|
(make-buffered-input-port-handler
|
|
(lambda (location)
|
|
(list 'tracking-port (port-location-sub-port location)))
|
|
(lambda (location) ; nothing to do
|
|
(values))
|
|
(lambda (location buffer start needed)
|
|
(update-row-and-column! location) ; finish off old buffer
|
|
(let ((res (read-block buffer start needed
|
|
(port-location-sub-port location))))
|
|
(cond ((eof-object? res))
|
|
((eq? buffer (port-buffer (port-location-port location)))
|
|
(set-port-location-index! location 0))
|
|
((code-vector? buffer)
|
|
(update-row-and-column-from-bytes! buffer start res location))
|
|
(else
|
|
(update-row-and-column-from-chars! buffer start res location)))
|
|
res))
|
|
(lambda (port)
|
|
(char-ready? (port-location-sub-port (port-data port))))))
|
|
|
|
(define (make-tracking-input-port port)
|
|
(if (input-port? port)
|
|
(let ((new-port
|
|
(make-buffered-input-port tracking-input-port-handler
|
|
(make-port-location port)
|
|
(make-code-vector default-buffer-size 0)
|
|
0
|
|
0)))
|
|
; make the circular link
|
|
(set-port-location-port! (port-data new-port) new-port)
|
|
new-port)
|
|
(call-error "not an input port" make-tracking-input-port port)))
|
|
|
|
;----------------
|
|
; Output ports that keep track of the current row and column.
|
|
|
|
(define tracking-output-port-handler
|
|
(make-buffered-output-port-handler
|
|
(lambda (location)
|
|
(list 'tracking-port (port-location-sub-port location)))
|
|
; flush the buffer when closing
|
|
(lambda (location)
|
|
(let ((port (port-location-port location)))
|
|
(if (< 0 (port-index port))
|
|
(write-block (port-buffer port) 0 (port-index port)
|
|
(port-location-sub-port location)))))
|
|
; Finish off the old buffer, send the characters to the child port, and
|
|
; then update the row and column if necessary.
|
|
(lambda (location buffer start count)
|
|
(update-row-and-column! location)
|
|
(write-block buffer start count (port-location-sub-port location))
|
|
(cond ((eq? buffer (port-buffer (port-location-port location)))
|
|
(set-port-location-index! location 0))
|
|
((code-vector? buffer)
|
|
(update-row-and-column-from-bytes! buffer start count location))
|
|
(else
|
|
(update-row-and-column-from-chars! buffer start count location))))
|
|
(lambda (port)
|
|
(output-port-ready? (port-location-sub-port (port-data port))))))
|
|
|
|
(define (make-tracking-output-port port)
|
|
(if (output-port? port)
|
|
(let ((new-port (make-output-port tracking-output-port-handler
|
|
(make-port-location port)
|
|
(make-code-vector default-buffer-size 0)
|
|
0
|
|
default-buffer-size)))
|
|
; make the circular link
|
|
(set-port-location-port! (port-data new-port) new-port)
|
|
new-port)
|
|
(call-error "not an output port" make-tracking-output-port port)))
|
|
|
|
(define (fresh-line port)
|
|
(let ((column (current-column port)))
|
|
(if (and column (< 0 column))
|
|
(newline port))))
|
|
|
|
;----------------
|
|
; String input ports
|
|
|
|
; All the work is done by the port code.
|
|
|
|
(define string-input-port-handler
|
|
(make-buffered-input-port-handler
|
|
(lambda (ignore)
|
|
(list 'string-input-port))
|
|
(lambda (ignore)
|
|
(values))
|
|
(lambda (ignore buffer start needed)
|
|
(eof-object))
|
|
(lambda (port) #f)))
|
|
|
|
(define (make-string-input-port string)
|
|
(let ((buffer (make-code-vector (string-length string) 0)))
|
|
(copy-bytes! string 0 buffer 0 (string-length string))
|
|
(make-buffered-input-port string-input-port-handler
|
|
#f ; no additional state needed
|
|
buffer
|
|
0
|
|
(string-length string)))) ; number of bytes available
|
|
|
|
(define copy-bytes! (structure-ref primitives copy-bytes!))
|
|
|
|
;----------------
|
|
; String output ports
|
|
|
|
; The data field of the port is a list of (<buffer> . <char-count>) pairs
|
|
; (the car is the port itself). When the output is wanted the buffers are
|
|
; concatenated together to get the final string.
|
|
|
|
(define buffer-size 1024)
|
|
|
|
; Concatenates all of the buffers into single string.
|
|
|
|
(define (string-output-port-output port)
|
|
(let* ((full (cdr (port-data port)))
|
|
(last (port-buffer port))
|
|
(index (port-index port))
|
|
(count (apply + index (map cdr full))) ; Scheme is a trip
|
|
(out (make-string count)))
|
|
(let loop ((full (reverse full)) (i 0))
|
|
(if (null? full)
|
|
(copy-bytes! last 0 out i index)
|
|
(let ((buffer (caar full))
|
|
(count (cdar full)))
|
|
(copy-bytes! buffer 0 out i count)
|
|
(loop (cdr full) (+ i count)))))
|
|
out))
|
|
|
|
(define string-output-port-handler
|
|
(make-buffered-output-port-handler
|
|
(lambda (port)
|
|
'(string-output-port))
|
|
(lambda (port)
|
|
(values))
|
|
(lambda (data thing start count)
|
|
(let ((port (car data)))
|
|
(set-cdr! (port-data port)
|
|
(cons (cons (full-buffer port thing start count)
|
|
count)
|
|
(cdr (port-data port))))))
|
|
(lambda (port) #f)))
|
|
|
|
(define (full-buffer port thing start count)
|
|
(cond ((eq? thing (port-buffer port))
|
|
(set-port-buffer! port (make-code-vector default-buffer-size 0))
|
|
thing)
|
|
(else
|
|
(let ((b (make-code-vector count 0)))
|
|
(copy-bytes! thing start b 0 count)
|
|
b))))
|
|
|
|
(define (make-string-output-port)
|
|
(let ((port (make-buffered-output-port
|
|
string-output-port-handler
|
|
(list #f)
|
|
(make-code-vector default-buffer-size 0)
|
|
0
|
|
default-buffer-size)))
|
|
(set-car! (port-data port) port)
|
|
port))
|
|
|
|
(define (call-with-string-output-port proc)
|
|
(let ((port (make-string-output-port)))
|
|
(proc port)
|
|
(string-output-port-output port)))
|
|
|
|
;----------------
|
|
; Output ports from single character consumers
|
|
|
|
(define char-sink-output-port-handler
|
|
(make-port-handler
|
|
(lambda (proc)
|
|
(list 'char-sink-output-port))
|
|
(lambda (proc)
|
|
(values))
|
|
(lambda (proc char)
|
|
(proc char))
|
|
(lambda (port) #t)))
|
|
|
|
(define (char-sink->output-port proc)
|
|
(make-unbuffered-output-port char-sink-output-port-handler
|
|
proc))
|
|
|
|
; Call PROC on a port that will transfer COUNT characters to PORT and
|
|
; then quit.
|
|
|
|
(define (write-one-line port count proc)
|
|
(call-with-current-continuation
|
|
(lambda (quit)
|
|
(proc (char-sink->output-port
|
|
(lambda (char)
|
|
(write-char char port)
|
|
(set! count (- count 1))
|
|
(if (<= count 0)
|
|
(quit #f))))))))
|
|
|
|
;----------------
|
|
; Input ports from single character producers
|
|
; The producer is passed #T if a character is needed and #F if not.
|
|
; If #F is passed and no character is ready, then #F is returned.
|
|
|
|
(define char-source-input-port-handler
|
|
(make-port-handler
|
|
(lambda (proc)
|
|
(list 'char-source-input-port))
|
|
(lambda (proc) ; nothing to do
|
|
(values))
|
|
(lambda (proc buffer start needed)
|
|
(if (integer? needed)
|
|
(let loop ((got 0))
|
|
(if (= got needed)
|
|
got
|
|
(let ((next (proc #t)))
|
|
(cond ((char? next)
|
|
(buffer-set! buffer (+ start got) next)
|
|
(loop (+ got 1)))
|
|
((= got 0)
|
|
(eof-object))
|
|
(else
|
|
got)))))
|
|
(let ((next (proc (eq? needed 'any))))
|
|
(cond ((not next)
|
|
0)
|
|
((eof-object? next)
|
|
(eof-object))
|
|
(else
|
|
(buffer-set! buffer start next)
|
|
1)))))
|
|
(lambda (port)
|
|
(error "Peek on char-source is not implemented. Complain to the scsh implementors"))))
|
|
|
|
|
|
(define (buffer-set! buffer index char)
|
|
(if (string? buffer)
|
|
(string-set! buffer index char)
|
|
(code-vector-set! buffer index (char->ascii char))))
|
|
|
|
(define (char-source->input-port proc)
|
|
(make-input-port char-source-input-port-handler
|
|
proc
|
|
(make-code-vector 1 0)
|
|
0
|
|
0))
|