; 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 ( . ) 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))