lqml/examples/cl-repl/lisp/eval.lisp
2022-04-09 19:12:45 +02:00

176 lines
6.9 KiB
Common Lisp

(defpackage :eval
(:use :cl :qml)
(:export
#:*eval-thread*
#:*query-dialog*
#:*debug-dialog*
#:ini
#:clear-buffers
#:feed-top-level
#:set-eval-state))
(in-package :eval)
(defvar *standard-output-buffer* (make-string-output-stream))
(defvar *trace-output-buffer* (make-string-output-stream))
(defvar *error-output-buffer* (make-string-output-stream))
(defvar *terminal-out-buffer* (make-string-output-stream))
(defvar *silent* nil)
(defvar *debug-invoked* nil)
(defvar *query-invoked* nil)
(defvar *eval-thread* nil)
(defvar *gui-debug-io* nil)
(defvar *gui-output* nil)
(defvar *gui-query-dialog* nil)
(defvar *gui-debug-dialog* nil)
(defvar qml::*reloading-qml* nil)
(defun ini (&key output query-dialog debug-dialog)
(setf *gui-output* output
*gui-query-dialog* query-dialog
*gui-debug-dialog* debug-dialog)
(ini-streams)
(setf *debug-io* *gui-debug-io*
*standard-input* *query-io*))
(defun ini-streams ()
(if *silent*
(setf *standard-output* *standard-output-buffer*
*trace-output* *trace-output-buffer*
*error-output* *error-output-buffer*)
(setf *standard-output* (make-broadcast-stream *standard-output*
*standard-output-buffer*)
*trace-output* (make-broadcast-stream *trace-output*
*trace-output-buffer*)
*error-output* (make-broadcast-stream *error-output*
*error-output-buffer*)))
(setf *terminal-io* (make-two-way-stream (two-way-stream-input-stream *terminal-io*)
(if *silent*
*terminal-out-buffer*
(make-broadcast-stream (two-way-stream-output-stream *terminal-io*)
*terminal-out-buffer*)))
*query-io* (make-two-way-stream (input-hook:add 'handle-query-io)
(two-way-stream-output-stream *terminal-io*))
*gui-debug-io* (make-two-way-stream (input-hook:add 'handle-debug-io)
(two-way-stream-output-stream *terminal-io*))))
(defun current-package-name ()
(if (eql (find-package :cl-user) *package*)
"CL-USER"
(car (sort (list* (package-name *package*) (package-nicknames *package*))
(lambda (x y) (< (length x) (length y)))))))
(defun write-output (type var)
(let ((str (get-output-stream-string var)))
(unless (x:empty-string str)
(when (eql :output type)
(let ((pos-gt (position #\> str))
(pos-nl (position #\Newline str)))
;; cut off prompt (always present on e.g. android)
;; otherwise cut off newline only
(setf str (subseq str (cond (pos-gt (+ pos-gt 2))
(pos-nl (+ pos-nl 1))
(t 0))))))
(when (and *gui-output*
(not (x:empty-string str)))
(funcall *gui-output* type str)))))
(let ((n 0))
(defun feed-top-level (str)
(unless (x:empty-string str)
(let ((pkg (if (zerop n) "QML-USER" (current-package-name)))
(counter (princ-to-string (incf n))))
(format t "~A [~A]~%~A"
pkg
counter
str))
(multiple-value-bind (x end)
(ignore-errors (read-from-string str))
(declare (ignore x))
;; use LOAD if there is more than 1 form
(setf si::*read-string*
(if (eql (length str) end)
str
(format nil "(load (make-string-input-stream ~S))" str))))
;; run eval in its own thread, so GUI will remain responsive
(set-eval-state t)
(setf *debug-invoked* nil
*query-invoked* nil)
(write-output :expression *standard-output-buffer*)
(setf si::*uncaught-exception* t) ; for case of silent stack overflow (arm only)
(setf *eval-thread* (mp:process-run-function "REPL eval" 'start-top-level)))))
(defun start-top-level ()
(si::%top-level)
(setf *eval-thread* nil)
(qsingle-shot 10 'top-level-exited))
(defun top-level-exited ()
(write-output :trace *trace-output-buffer*)
(write-output :output *standard-output-buffer*)
(write-output :error *error-output-buffer*)
(when (and *gui-output*
(or (not *debug-invoked*)
(and *debug-invoked*
*query-invoked*)))
(funcall *gui-output* :values
(if si::*uncaught-exception*
":UNCAUGHT-EXCEPTION"
(format nil "~{~S~^#||#~}" si::*latest-values*)))) ; "#||#": separator
(unless qml::*reloading-qml*
(set-eval-state nil)))
(defun show-progress-bar (&optional (show t))
(q> |visible| ui:*progress* show))
(defun set-eval-state (evaluating)
(unless qml::*reloading-qml*
(q> |enabled| ui:*eval*
(if (find-package :swank) t (not evaluating)))
(show-progress-bar evaluating)))
(defun clear-buffers ()
(dolist (s (list *standard-output-buffer*
*trace-output-buffer*
*error-output-buffer*
*terminal-out-buffer*))
(get-output-stream-string s))
(q> |text| ui:*query-text* ""))
(defun handle-query-io ()
(setf *query-invoked* t)
(let ((text (funcall *gui-query-dialog*
(get-output-stream-string *terminal-out-buffer*))))
(when (and *gui-output*
(not (x:empty-string text)))
(funcall *gui-output* :values text))
(format nil "~A~%" text)))
(defun find-quit-restart ()
;; find best restart for ':q' (default) to exit the debugger
(let ((restarts (compute-restarts)))
(if (= 1 (length restarts))
":r1"
(let ((restart-names (mapcar (lambda (r)
(symbol-name (restart-name r)))
restarts)))
;; precedence role
(dolist (name '("RESTART-TOPLEVEL"
"ABORT"
"RESTART-QT-EVENTS"))
(x:when-it (position name restart-names :test 'string=)
(return-from find-quit-restart (format nil ":r~D" x:it)))))))
":q")
(defun handle-debug-io ()
(set-eval-state nil)
(setf *debug-invoked* t)
(let ((cmd (funcall *gui-debug-dialog*
(list (cons (get-output-stream-string *error-output-buffer*)
"#d00000")
(cons (get-output-stream-string *terminal-out-buffer*)
"black")))))
(when (string-equal ":q" cmd)
(setf cmd (find-quit-restart)))
(format nil "~A~%" (if (x:empty-string cmd) ":q" cmd))))