(module acl2-process mzscheme
(require (lib "async-channel.ss")
(lib "process.ss")
(lib "class.ss")
(lib "port.ss")
(lib "etc.ss")
(lib "moddep.ss" "syntax")
(lib "mred.ss" "mred")) (require "../language/find-acl2.scm"
"../language/acl2-module-v.scm"
"windows-interrupt.scm"
"../language/acl2-path-to-lisp-command.scm")
(provide acl2-process%)
(error-print-width 500)
(require (file "eprintf.scm"))
(define err-port (current-error-port))
(define stars-re "\\*\\*\\*\\*\\*\\*\\*\\*") (define failure-re
(string-append stars-re " FAILED " stars-re
" See :DOC failure "
stars-re " FAILED " stars-re))
(define failure-str
"******** FAILED ******** See :DOC failure ******** FAILED ********")
(define error-re "ACL2 Error in")
(define raw-lisp-re "RAW LISP")
(define raw-lisp-bytes #"RAW LISP")
(define prompt-re "ACL2 !>")
(define proof-tree-start-re "#<\\\\<0")
(define proof-tree-start-bytes #"#<\\<0")
(define proof-tree-stop-re "#>\\\\>")
(define done-str "|||this-symbol-indicates-that-ACL2-is-done|||")
(define done-bytes #"|\\|\\|\\|this-symbol-indicates-that-ACL2-is-done\\|\\|\\||")
(define done-sym (string->symbol done-str))
(define done-re "\\|\\\\\\|\\\\\\|\\\\\\|this-symbol-indicates-that-ACL2-is-done\\\\\\|\\\\\\|\\\\\\|\\|")
(define (send-done port)
(write `(intern ,done-str "ACL2") port))
(define fail-or-error-or-prompt-re
(regexp (string-append failure-re "|" error-re "|" prompt-re)))
(define fail-or-error-or-done-re
(regexp (string-append failure-re "|" error-re "|" done-re)))
(define pftree/done-re
(regexp (string-append proof-tree-start-re "|" done-re)))
(define fail/error/pftree/done-re
(regexp (string-append failure-re "|"
error-re "|"
proof-tree-start-re "|" done-re)))
(define (copy-rest in-> ->out)
(regexp-match done-re in-> 0 #f ->out)
(regexp-match prompt-re in-> 0 #f ->out)
(let ([tid (thread (lambda () (copy-port in-> ->out)))])
(sync/timeout/enable-break 2 tid)
(kill-thread tid)))
(define (extract-prooftree from->)
(let ([proof-tree-port (open-output-string)])
(regexp-match proof-tree-stop-re from-> 0 #f proof-tree-port)
(get-output-string proof-tree-port)))
(define (spawn/connect-output status-channel acl2-> ->console send-new-prooftree)
(thread
(lambda ()
(regexp-match "Initialized with" acl2-> 0 #f ->console)
(regexp-match prompt-re acl2-> 0 #f)
(let loop ()
(let* ([matched
(let ([m (regexp-match fail/error/pftree/done-re
acl2-> 0 #f ->console)])
(if m
(car m)
(raise-user-error
'acl2-output-thread
"Output matches none of fail|error|pftree|done")))]
[status
(cond [(equal? matched done-bytes) #t]
[(equal? matched proof-tree-start-bytes) 'proof-tree-start]
[(equal? matched raw-lisp-bytes) 'raw-lisp]
[else #f])])
(case status
[(proof-tree-start)
(send-new-prooftree (extract-prooftree acl2->))]
[(raw-lisp) '???]
[(#t) (async-channel-put status-channel #t)]
[(#f) (begin
(display matched ->console)
(flush-acl2-input acl2-> ->console send-new-prooftree)
(async-channel-put status-channel #f))])
(loop))))))
(define (flush-acl2-input acl2-> ->console send-new-prooftree)
(let loop ()
(let* ([matched (car (regexp-match pftree/done-re
acl2-> 0 #f ->console))]
[status (cond [(equal? matched done-bytes) 'done]
[(equal? matched proof-tree-start-bytes) 'proof-tree-start])])
(case status
[(done) 'done]
[(proof-tree-start)
(begin (send-new-prooftree (extract-prooftree acl2->))
(loop))]))))
(define acl2-process%
(class object% ()
(init-field send-new-prooftree)
(init-field [acl2-output-port err-port])
(init-field [path (find-acl2)])
(field [status-channel (make-async-channel)])
(define process-alive? #f)
(define-values (*acl2->me* *me->acl2* *PID* *acl2-error->me* *acl2-control*)
(values #f #f #f #f #f))
(define output-thread #f)
(define/private (initialize-acl2-environment!)
(for-each (lambda (x) (send this send-to-repl x))
`(:start-proof-tree
(set-ld-pre-eval-print t state)
(add-include-book-dir :teachpacks ,teachpack-path))))
(define/public (start!)
(when (not process-alive?)
(set!-values (*acl2->me* *me->acl2* *PID* *acl2-error->me* *acl2-control*)
(apply values
(apply process* (acl2-path->command-list path))))
(case (*acl2-control* 'status)
[(running)
(begin
(set! process-alive? #t)
(set! output-thread
(spawn/connect-output status-channel *acl2->me* acl2-output-port
send-new-prooftree))
(initialize-acl2-environment!))]
[else (raise-user-error 'acl2-process%::start
"Failed to start ACL2 (using this path: ~a)"
path)])
*PID*))
(define/public (stop!)
(when *acl2-control*
(when output-thread (kill-thread output-thread))
(*acl2-control* 'kill)
(close-input-port *acl2->me*)
(close-input-port *acl2-error->me*)
(close-output-port *me->acl2*)
(set!-values (process-alive? *acl2->me* *me->acl2* *PID*
*acl2-error->me* *acl2-control*)
(apply values (build-list 6 (lambda _ #f))))
))
(define error-str
(string-append
"Dracula currently only works on Windows, Unix, and "
"Mac OS X. You appear to be running on ~a. Please file"
" a bug report and include this message!"))
(define/public (interrupt)
(case (system-type 'os)
[(windows)
(begin (windows-interrupt path *PID*)
(send-to-repl "#."))]
[(unix macosx)
(begin (*acl2-control* 'interrupt)
(send-to-repl "#."))]
[else (raise-user-error 'Interrupt-ACL2
error-str
(system-type 'os))]))
(define/public (undo-last)
(send-to-repl ':u))
(define/public (reset-acl2)
(string->repl ":ubt! 1")
(initialize-acl2-environment!))
(define/public (send-to-repl s-exp)
(when (not process-alive?)
(start!))
(if (string? s-exp)
(string->repl s-exp)
(s-exp->repl s-exp)))
(define (string->repl str)
(write-string str *me->acl2*)
(newline *me->acl2*)
(send-done *me->acl2*)
(newline *me->acl2*)
(flush-output *me->acl2*)
(yield status-channel))
(define/public (s-exp->repl s-exp)
(write s-exp *me->acl2*)
(newline *me->acl2*)
(send-done *me->acl2*)
(newline *me->acl2*)
(flush-output *me->acl2*)
(yield status-channel))
(super-new)))
)