- reimplemented interact with expect
This commit is contained in:
parent
7642842a3a
commit
c7f21da61a
|
@ -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 ...))))))
|
|
||||||
|
|
Loading…
Reference in New Issue