- reimplemented interact with expect

This commit is contained in:
frese 2004-08-24 13:22:33 +00:00
parent 7642842a3a
commit c7f21da61a
1 changed files with 126 additions and 87 deletions

View File

@ -1,95 +1,134 @@
;; (interact* task [input-filter output-filter]) ;; TODO: interpreter?
;;
(define (interact* task . args) ;; The pattern eof introduces an action that is executed upon end-
(let-optionals args ((input-filter (lambda (k x) x)) ;; of-file. A separate eof pattern may also follow the -output flag
(output-filter (lambda (k x) x))) ;; in which case it is matched if an eof is detected while writing
(let ((ivec (make-vector 2)) ;; output. The default eof action is "return", so that interact
(ivec0 (current-input-port)) ;; simply returns upon any EOF.
(ivec1 (task:in task))
(out0 (current-output-port)) ;; The pattern timeout introduces a timeout (in seconds) and action
(out1 (task:out task))) ;; that is executed after no characters have been read for a given
;; time.
;; The -echo flag sends characters that match the following pattern
;; back to the process that generated them as each character is read.
;; This may be useful when the user needs to see feedback from
;; partially typed patterns.
;; The -nobuffer flag sends characters that match the following pat-
;; tern on to the output process as characters are read.
(define-record-type :eof-pattern
(make-eof-pattern)
eof-pattern?)
(define eof-pattern (make-eof-pattern))
(define (interact* task re-flags-handler-list timeout-handler)
(let* ((user-in (current-input-port))
(user-out (current-output-port))
(user-task (user-task))
(tty-before (tty-info user-in))
(init-tty (lambda ()
(set! tty-before (tty-info user-in))
(modify-tty (lambda (ti) (raw (echo-off ti))) user-in)))
(reset-tty (lambda ()
(set-tty-info/now user-in tty-before))))
;; TODO: if no tty??
(init-tty)
(call-with-current-continuation (call-with-current-continuation
(lambda (k) (lambda (k)
(with-errno-handler (let ((conv
((e p) ((errno/io) #f)) ;; TODO: eof in input/output (lambda (loop)
(let lp () (lambda (re-flags-handler)
(vector-set! ivec 0 ivec0) (let* ((re (car re-flags-handler))
(vector-set! ivec 1 ivec1) (re (cond
(select! ivec '#() '#()) ((string? re) (rx ,re))
(if (char-ready? ivec0) ((char? re) (rx ,(make-string 1 re)))
(let ((str (input-filter k (read-string/partial 256 ivec0)))) (else re)))
(if str (write-string str out1)))) (flags (cadr re-flags-handler))
(if (char-ready? ivec1) (handler (caddr re-flags-handler))
(let ((str (output-filter k (read-string/partial 256 ivec1)))) (before (lambda ()
(if str (write-string str out0)))) (if (memq 'reset flags) (reset-tty))))
(lp)))))))) (after (lambda ()
(if (memq 'reset flags) (init-tty)))))
(cond
((eq? eof-pattern re)
(list 'eof
(lambda ()
(before)
(handler k #f)
(after))))
(else
(list 'match
(lambda () re)
(lambda (m)
(before)
(handler k m)
(after)
(loop))))))))))
(let-values (((outputs inputs)
(partition (lambda (re-flag-handler)
(memq 'output (second re-flag-handler)))
re-flags-handler-list)))
(let ((output-else-rx (else-rx (map car outputs)))
(input-else-rx (else-rx (map car inputs))))
(let loop ()
(let ((timeout (if timeout-handler
(list (cons 'timeout (car timeout-handler))
(cons 'on-timeout
(lambda ()
((cdr timeout-handler) k)
(loop))))
'((timeout . #f)))))
(expect* timeout
(cons user-task
(cons (list 'match (lambda () input-else-rx)
(lambda (m)
(write-string (match:substring m)
(task:out task))
(loop)))
(map (conv loop) inputs)))
(cons task
(cons (list 'match (lambda () output-else-rx)
(lambda (m)
(write-string (match:substring m)
(task:out user-task)
)
(loop)))
(map (conv loop) outputs)))))))))))
(reset-tty)))
(define (interact/char* task . args)
(let-optionals args ((input-filter #f)
(output-filter #f))
(interact* task
(lambda (k str)
(if input-filter
;; TODO: does this preserve correct order?
(let ((chars (filter (lambda (c) (input-filter k c))
(string->list str))))
(list->string chars))
str))
(lambda (k str)
(if output-filter
(let ((chars (filter (lambda (c) (output-filter k c))
(string->list str))))
(list->string chars))
str)))))
;; syntax ;; returns a pattern, that matches anything but the given patterns
;;;; Allow user to interact with program (sometimes there's just no other way) (define (else-rx patterns)
;;;; (interact task) simply switches the user into interact mode with the task. ;; only character-patterns supported
;;;; (if (null? patterns)
;;;; (interact task clause ...) filters the input. The clause can either be a (rx any)
;;;; character clause (character continuation-variable exp ...) or a filter (let ((p (car patterns))
;;;; clause (filter proc). A character clause matches the character given, (rest (cdr patterns)))
;;;; links the continuation variable to the continuation out of the (cond
;;;; interaction, and runs the expressions. A filter clause runs the given ((char? (car patterns))
;;;; filter-function with two arguments: the character entered and the (rx (- ,(else-rx rest) ,p)))
;;;; continuation out of the interaction. In both cases, if the filter returns (else (error "Only character-patterns are supported."))))))
;;;; true, the expression falls through. If the expression falls through to
;;;; the bottom, the character is printed. Note that the interaction will
;;;; continue anyway, unless the continuation was explicitly called.
(define-syntax interact-input-clauses (define-syntax interact-clauses
(syntax-rules (filter eof) (syntax-rules (timeout)
((interact-input-clauses break char) ((interact-clauses) (cons #f '()))
#t) ((interact-clauses (timeout secs handler) rest ...)
((interact-input-clauses break char (eof k body ...) clause2 ...) (let ((r (interact-clauses rest ...)))
(if (eof-object? char) (cons (cons secs handler)
(let ((k break)) (cdr r))))
(begin body ...)) ((interact-clauses (rx (flag ...) (cont match mvar ...) body ...) rest ...)
(interact-input-clauses break char clause2 ...))) (let ((r (interact-clauses rest ...)))
((interact-input-clauses break char (test-char k body ...) clause2 ...) (cons (car r)
(if (eq? char test-char) (cons (list rx `(flag ...) (lambda (cont match)
(let ((k break)) (let-match match (mvar ...)
(begin body ...)) body ...)))
(interact-input-clauses break char clause2 ...))) (cdr r)))))))
((interact-input-clauses break char (filter proc) clause2 ...)
(and (filter break char)
(interact-input-clauses break char clause2 ...)))))
(define-syntax interact-output-clauses
(syntax-rules (filter eof)
((interact-output-clauses break char)
#t)
((interact-output-clauses break char clause ...)
#t)))
(define-syntax interact (define-syntax interact
(syntax-rules () (syntax-rules ()
((interact task) ((interact task iclause ...)
(interact* task)) (let ((r (interact-clauses iclause ...)))
((interact task clause ...) (interact* task (cdr r) (car r))))))
(interact/char* task
(lambda (k c)
(interact-input-clauses k c clause ...))
(lambda (k c)
(interact-output-clauses k c clause ...))))))