aboutsummaryrefslogtreecommitdiffstats
path: root/Lisp/moxie/repl.lisp
blob: b5b7bb3e538dd4baf3e555cfd8486e3e380d1a0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
(in-package :moxie)

(defvar *repl-motd*
  "Welcome to Moxie!

To get help, enter :HELP at the prompt.")

(defvar *repl-help*
  "Top level commands:
    :R [num]     Invoke restart NUM, or list restarts.
    :HELP :H :?  Display this message.")

(defvar *repl-level* 0)

(defun start-repl (&optional (use-result-stream t))
  (let ((*moxie-result-stream* (or (and use-result-stream (make-result-stream))
                                   *error-output*)))
    (format t "~%~A~%" *repl-motd*)
    (send-command :repl-result `(:prompt ,(repl-prompt)))
    (repl)))

(defun repl ()
  "This is Moxie's top level loop. At this point, it's only here
because we don't want the host lisp to print results or its prompt."
  (let* ((*debugger-hook* #'repl-dbg)
         (*repl-level* (1+ *repl-level*))
         (lex-level *repl-level*))
    (loop
       (force-output)
       (let ((form (read)))
         (restart-case (eval form)
           (abort ()
             :report (lambda (stream)
                       ;; I know this looks weird, but because the
                       ;; formatter is called from the condition
                       ;; handler's environment, and because
                       ;; *repl-level* is special, at the time of
                       ;; evaluation, *repl-level* may be higher than
                       ;; lex-level.
                       (if (eql lex-level *repl-level*)
                           (format stream "Abort handling of current request.")
                           (format stream "Return to REPL level ~A."
                                   lex-level)))
             (send-command :repl-result `(:prompt ,(repl-prompt)))))))))

(defun repl-dbg (condition debugger-hook)
  "This debugger hook just sends a message to Moxie when the debugger
has been entered, so Moxie can keep track of the prompt."
  (declare (ignore debugger-hook))
  (send-command :repl-dbg `(:condition ,condition)))

(defmacro eval-hook (&rest forms)
  "Ensure all FORMS are valid for evaluation before calling
EVAL-HOOK-HELPER."
  (let ((helped-forms (mapcar (lambda (x) `(quote ,x)) forms)))
    `(eval-hook-helper ,@helped-forms)))

(defun eval-hook-helper (&rest forms)
  "Evaluate all FORMS, sending the results to the Moxie output
stream. When finished processing, send the prompt."
  (do* ((f forms (cdr f))
        (form (car f) (car f)))
       ((null f))
    (case form
      (:r (let ((restarts (compute-restarts))
                (num (cadr f)))
            (if (and (integerp num)
                     (> num 0) (<= num (length restarts)))
                (progn
                  (setf f (cdr f))
                  (invoke-restart (elt restarts (1- num))))
                (print-restarts restarts))))
      ((:? :h :help) (format t "~A~%" *repl-help*))
      (t (let (values)
           (setq - form)
           (setq values (multiple-value-list (eval -)))
           (setq /// // // / / values *** ** ** * * (car /))
           (send-command :repl-result `(:values ,@values))))))
  (send-command :repl-result `(:prompt ,(repl-prompt))))

(defun print-restarts (restarts)
  (format t "Available restarts: ~%")
  (do ((c restarts (cdr c))
       (i 1 (1+ i)))
      ((null c))
    (format t " ~A ~A~%" i (car c)))
  (format t "Invoke restarts with :R [num]~%"))

(defun repl-prompt ()
  "Compute the prompt for Moxie's REPL."
  (format nil "~A~@[[~A]~]> "
          (if (eql *package* (find-package :cl-user))
              "CL-USER"
              (package-name *package*))
          (when (> *repl-level* 1) *repl-level*)))