1 | ;;; top-level.lisp |
---|
2 | ;;; |
---|
3 | ;;; Copyright (C) 2003-2006 Peter Graves |
---|
4 | ;;; $Id: top-level.lisp 15569 2022-03-19 12:50:18Z mevenson $ |
---|
5 | ;;; |
---|
6 | ;;; This program is free software; you can redistribute it and/or |
---|
7 | ;;; modify it under the terms of the GNU General Public License |
---|
8 | ;;; as published by the Free Software Foundation; either version 2 |
---|
9 | ;;; of the License, or (at your option) any later version. |
---|
10 | ;;; |
---|
11 | ;;; This program is distributed in the hope that it will be useful, |
---|
12 | ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
14 | ;;; GNU General Public License for more details. |
---|
15 | ;;; |
---|
16 | ;;; You should have received a copy of the GNU General Public License |
---|
17 | ;;; along with this program; if not, write to the Free Software |
---|
18 | ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
---|
19 | ;;; |
---|
20 | ;;; As a special exception, the copyright holders of this library give you |
---|
21 | ;;; permission to link this library with independent modules to produce an |
---|
22 | ;;; executable, regardless of the license terms of these independent |
---|
23 | ;;; modules, and to copy and distribute the resulting executable under |
---|
24 | ;;; terms of your choice, provided that you also meet, for each linked |
---|
25 | ;;; independent module, the terms and conditions of the license of that |
---|
26 | ;;; module. An independent module is a module which is not derived from |
---|
27 | ;;; or based on this library. If you modify this library, you may extend |
---|
28 | ;;; this exception to your version of the library, but you are not |
---|
29 | ;;; obligated to do so. If you do not wish to do so, delete this |
---|
30 | ;;; exception statement from your version. |
---|
31 | |
---|
32 | ;;; Adapted from SB-ACLREPL (originally written by Kevin Rosenberg). |
---|
33 | |
---|
34 | (in-package #:top-level) |
---|
35 | |
---|
36 | (require 'inspect) |
---|
37 | |
---|
38 | (defvar *null-cmd* (gensym)) |
---|
39 | (defvar *handled-cmd* (gensym)) |
---|
40 | |
---|
41 | (defvar *command-char* #\:) |
---|
42 | |
---|
43 | (defvar *cmd-number* 1 |
---|
44 | "Number of the next command") |
---|
45 | |
---|
46 | (defun prompt-package-name () |
---|
47 | (let ((result (package-name *package*))) |
---|
48 | (dolist (nickname (package-nicknames *package*)) |
---|
49 | (when (< (length nickname) (length result)) |
---|
50 | (setf result nickname))) |
---|
51 | result)) |
---|
52 | |
---|
53 | (defun repl-prompt-fun (stream) |
---|
54 | (fresh-line stream) |
---|
55 | (when (> *debug-level* 0) |
---|
56 | (sys::%format stream "[~D~A] " |
---|
57 | *debug-level* |
---|
58 | (if sys::*inspect-break* "i" ""))) |
---|
59 | (sys::%format stream "~A(~D): " (prompt-package-name) *cmd-number*)) |
---|
60 | |
---|
61 | (defparameter *repl-prompt-fun* #'repl-prompt-fun) |
---|
62 | |
---|
63 | (defun peek-char-non-whitespace (stream) |
---|
64 | (loop |
---|
65 | (let ((c (read-char stream nil))) |
---|
66 | (when (null c) ; control d |
---|
67 | (quit)) |
---|
68 | (unless (eql c #\space) |
---|
69 | (unread-char c stream) |
---|
70 | (return c))))) |
---|
71 | |
---|
72 | (defun apropos-command (args) |
---|
73 | (when args (apropos args))) |
---|
74 | |
---|
75 | (defun continue-command (args) |
---|
76 | (when args |
---|
77 | (let ((n (read-from-string args))) |
---|
78 | (let ((restarts (compute-restarts))) |
---|
79 | (when (< -1 n (length restarts)) |
---|
80 | (invoke-restart-interactively (nth n restarts))))))) |
---|
81 | |
---|
82 | (defun describe-command (args) |
---|
83 | (let ((obj (eval (read-from-string args)))) |
---|
84 | (describe obj))) |
---|
85 | |
---|
86 | (defun error-command (ignored) |
---|
87 | (declare (ignore ignored)) |
---|
88 | (when *debug-condition* |
---|
89 | (let* ((s (sys::%format nil "~A" *debug-condition*)) |
---|
90 | (len (length s))) |
---|
91 | (when (plusp len) |
---|
92 | (setf (schar s 0) (char-upcase (schar s 0))) |
---|
93 | (unless (eql (schar s (1- len)) #\.) |
---|
94 | (setf s (concatenate 'string s ".")))) |
---|
95 | (sys::%format *debug-io* "~A~%" s)) |
---|
96 | (show-restarts (compute-restarts) *debug-io*))) |
---|
97 | |
---|
98 | (defun print-frame (frame stream &key prefix) |
---|
99 | (when prefix |
---|
100 | (write-string prefix stream)) |
---|
101 | (etypecase frame |
---|
102 | (sys::lisp-stack-frame |
---|
103 | (let ((frame (sys:frame-to-list frame))) |
---|
104 | (pprint-logical-block (stream nil :prefix "(" :suffix ")") |
---|
105 | (ignore-errors |
---|
106 | (prin1 (car frame) stream) |
---|
107 | (let ((args (cdr frame))) |
---|
108 | (if (listp args) |
---|
109 | (format stream "~{ ~_~S~}" args) |
---|
110 | (format stream " ~S" args))))))) |
---|
111 | (sys::java-stack-frame |
---|
112 | (write-string (sys:frame-to-string frame) stream)))) |
---|
113 | |
---|
114 | |
---|
115 | (defun backtrace-command (args) |
---|
116 | (let ((count (or (and args (ignore-errors (parse-integer args))) |
---|
117 | 8)) |
---|
118 | (n 0)) |
---|
119 | (with-standard-io-syntax |
---|
120 | (let ((*print-pretty* t) |
---|
121 | (*print-readably* nil) |
---|
122 | (*print-structure* nil) |
---|
123 | (*print-array* nil)) |
---|
124 | (dolist (frame *saved-backtrace*) |
---|
125 | (fresh-line *debug-io*) |
---|
126 | (print-frame frame *debug-io* :prefix (format nil "~3D: " n)) |
---|
127 | (incf n) |
---|
128 | (when (>= n count) |
---|
129 | (fresh-line *debug-io*) |
---|
130 | (return)))))) |
---|
131 | (fresh-line *debug-io*) |
---|
132 | (values)) |
---|
133 | |
---|
134 | (defun frame-command (args) |
---|
135 | (let* ((n (or (and args (ignore-errors (parse-integer args))) |
---|
136 | 0)) |
---|
137 | (frame (nth n *saved-backtrace*))) |
---|
138 | (when frame |
---|
139 | (with-standard-io-syntax |
---|
140 | (let ((*print-pretty* t) |
---|
141 | (*print-readably* nil) |
---|
142 | (*print-structure* nil)) |
---|
143 | (fresh-line *debug-io*) |
---|
144 | (print-frame frame *debug-io*))) |
---|
145 | (setf *** ** |
---|
146 | ** * |
---|
147 | * frame))) |
---|
148 | (values)) |
---|
149 | |
---|
150 | (defun inspect-command (args) |
---|
151 | (let ((obj (eval (read-from-string args)))) |
---|
152 | (inspect obj))) |
---|
153 | |
---|
154 | (defun istep-command (args) |
---|
155 | (sys::istep args)) |
---|
156 | |
---|
157 | (defun macroexpand-command (args) |
---|
158 | (let ((s (with-output-to-string (stream) |
---|
159 | (pprint (macroexpand (read-from-string args)) stream)))) |
---|
160 | (write-string (string-left-trim '(#\return #\linefeed) s))) |
---|
161 | (values)) |
---|
162 | |
---|
163 | (defvar *old-package* nil) |
---|
164 | |
---|
165 | (defun package-command (args) |
---|
166 | (cond ((null args) |
---|
167 | (sys::%format *standard-output* "The ~A package is current.~%" |
---|
168 | (package-name *package*))) |
---|
169 | ((and *old-package* (string= args "-") (null (find-package "-"))) |
---|
170 | (rotatef *old-package* *package*)) |
---|
171 | (t |
---|
172 | (when (and (plusp (length args)) (eql (char args 0) #\:)) |
---|
173 | (setf args (subseq args 1))) |
---|
174 | (setf args (nstring-upcase args)) |
---|
175 | (let ((pkg (find-package args))) |
---|
176 | (if pkg |
---|
177 | (setf *old-package* *package* |
---|
178 | *package* pkg) |
---|
179 | (sys::%format *standard-output* "Unknown package ~A.~%" args)))))) |
---|
180 | |
---|
181 | (defun reset-command (ignored) |
---|
182 | (declare (ignore ignored)) |
---|
183 | (invoke-restart 'top-level)) |
---|
184 | |
---|
185 | (defun exit-command (ignored) |
---|
186 | (declare (ignore ignored)) |
---|
187 | (exit)) |
---|
188 | |
---|
189 | (defvar *old-pwd* nil) |
---|
190 | |
---|
191 | (defun cd-command (args) |
---|
192 | (cond ((null args) |
---|
193 | (setf args (if (featurep :windows) |
---|
194 | "C:\\" |
---|
195 | (namestring (user-homedir-pathname))))) |
---|
196 | ((string= args "-") |
---|
197 | (if *old-pwd* |
---|
198 | (setf args (namestring *old-pwd*)) |
---|
199 | (progn |
---|
200 | (sys::%format t "No previous directory.") |
---|
201 | (return-from cd-command)))) |
---|
202 | ((and (> (length args) 1) (string= (subseq args 0 2) "~/") |
---|
203 | (setf args (concatenate 'string |
---|
204 | (namestring (user-homedir-pathname)) |
---|
205 | (subseq args 2)))))) |
---|
206 | (let ((dir (probe-directory args))) |
---|
207 | (if dir |
---|
208 | (progn |
---|
209 | (unless (equal dir *default-pathname-defaults*) |
---|
210 | (setf *old-pwd* *default-pathname-defaults* |
---|
211 | *default-pathname-defaults* dir)) |
---|
212 | (sys::%format t "~A" (namestring *default-pathname-defaults*))) |
---|
213 | (sys::%format t "Error: no such directory (~S).~%" args)))) |
---|
214 | |
---|
215 | (defun ls-command (args) |
---|
216 | (let ((args (if (stringp args) args "")) |
---|
217 | (ls-program (if (featurep :windows) "dir" "ls"))) |
---|
218 | (run-shell-command (concatenate 'string ls-program " " args) |
---|
219 | :directory *default-pathname-defaults*)) |
---|
220 | (values)) |
---|
221 | |
---|
222 | (defun tokenize (string) |
---|
223 | (do* ((res nil) |
---|
224 | (string (string-left-trim " " string) |
---|
225 | (string-left-trim " " (subseq string end))) |
---|
226 | (end (position #\space string) (position #\space string))) |
---|
227 | ((zerop (length string)) (nreverse res)) |
---|
228 | (unless end |
---|
229 | (setf end (length string))) |
---|
230 | (push (subseq string 0 end) res))) |
---|
231 | |
---|
232 | (defvar *last-files-loaded* nil) |
---|
233 | |
---|
234 | (defun ld-command (args) |
---|
235 | (let ((files (if args (tokenize args) *last-files-loaded*))) |
---|
236 | (setf *last-files-loaded* files) |
---|
237 | (dolist (file files) |
---|
238 | (load file)))) |
---|
239 | |
---|
240 | (defun cf-command (args) |
---|
241 | (let ((files (tokenize args))) |
---|
242 | (dolist (file files) |
---|
243 | (compile-file file)))) |
---|
244 | |
---|
245 | (defvar *last-files-cloaded* nil) |
---|
246 | |
---|
247 | (defun cload-command (args) |
---|
248 | (let ((files (if args (tokenize args) *last-files-cloaded*))) |
---|
249 | (setf *last-files-cloaded* files) |
---|
250 | (dolist (file files) |
---|
251 | (load (compile-file file))))) |
---|
252 | |
---|
253 | (defun rq-command (args) |
---|
254 | (let ((modules (tokenize (string-upcase args)))) |
---|
255 | (dolist (module modules) |
---|
256 | (require module)))) |
---|
257 | |
---|
258 | (defun pwd-command (ignored) |
---|
259 | (declare (ignore ignored)) |
---|
260 | (sys::%format t "~A~%" (namestring *default-pathname-defaults*))) |
---|
261 | |
---|
262 | (defun trace-command (args) |
---|
263 | (if (null args) |
---|
264 | (sys::%format t "~A~%" (sys::list-traced-functions)) |
---|
265 | (dolist (f (tokenize args)) |
---|
266 | (sys::trace-1 (read-from-string f))))) |
---|
267 | |
---|
268 | (defun untrace-command (args) |
---|
269 | (if (null args) |
---|
270 | (sys::untrace-all) |
---|
271 | (dolist (f (tokenize args)) |
---|
272 | (sys::untrace-1 (read-from-string f))))) |
---|
273 | |
---|
274 | (defconstant spaces (make-string 32 :initial-element #\space)) |
---|
275 | |
---|
276 | (defun pad (string width) |
---|
277 | (if (< (length string) width) |
---|
278 | (concatenate 'string string (subseq spaces 0 (- width (length string)))) |
---|
279 | string)) |
---|
280 | |
---|
281 | (defparameter *command-table* |
---|
282 | '(("apropos" "ap" apropos-command "apropos") |
---|
283 | ("bt" nil backtrace-command "backtrace n stack frames (default 8)") |
---|
284 | ("cd" nil cd-command "change default directory") |
---|
285 | ("cf" nil cf-command "compile file(s)") |
---|
286 | ("cload" "cl" cload-command "compile and load file(s)") |
---|
287 | ("continue" "cont" continue-command "invoke restart n") |
---|
288 | ("describe" "de" describe-command "describe an object") |
---|
289 | ("error" "err" error-command "print the current error message") |
---|
290 | ("exit" "ex" exit-command "exit lisp") |
---|
291 | ("frame" "fr" frame-command "set the value of cl:* to be frame n (default 0)") |
---|
292 | ("help" "he" help-command "print this help") |
---|
293 | ("inspect" "in" inspect-command "inspect an object") |
---|
294 | ("istep" "i" istep-command "navigate within inspection of an object") |
---|
295 | ("ld" nil ld-command "load a file") |
---|
296 | ("ls" nil ls-command "list directory") |
---|
297 | ("macroexpand" "ma" macroexpand-command "macroexpand an expression") |
---|
298 | ("package" "pa" package-command "change *PACKAGE*") |
---|
299 | ("pwd" "pw" pwd-command "print current directory") |
---|
300 | ("reset" "res" reset-command "return to top level") |
---|
301 | ("rq" nil rq-command "require a module") |
---|
302 | ("trace" "tr" trace-command "trace function(s)") |
---|
303 | ("untrace" "untr" untrace-command "untrace function(s)"))) |
---|
304 | |
---|
305 | (defun %help-command (prefix) |
---|
306 | (let ((prefix-len (length prefix))) |
---|
307 | (when (and (> prefix-len 0) |
---|
308 | (eql (schar prefix 0) *command-char*)) |
---|
309 | (setf prefix (subseq prefix 1)) |
---|
310 | (decf prefix-len)) |
---|
311 | (sys::%format t "~% COMMAND ABBR DESCRIPTION~%") |
---|
312 | (dolist (entry *command-table*) |
---|
313 | (when (or (null prefix) |
---|
314 | (and (<= prefix-len (length (entry-name entry))) |
---|
315 | (string-equal prefix (subseq (entry-name entry) 0 prefix-len)))) |
---|
316 | (sys::%format t " ~A~A~A~%" |
---|
317 | (pad (entry-name entry) 12) |
---|
318 | (pad (entry-abbreviation entry) 5) |
---|
319 | (entry-help entry)))) |
---|
320 | (sys::%format t "~%Commands must be prefixed by the command character, which is '~A'~A.~%~%" |
---|
321 | *command-char* (if (eql *command-char* #\:) " by default" "")))) |
---|
322 | |
---|
323 | (defun help-command (&optional ignored) |
---|
324 | (declare (ignore ignored)) |
---|
325 | (%help-command nil)) |
---|
326 | |
---|
327 | (defun entry-name (entry) |
---|
328 | (first entry)) |
---|
329 | |
---|
330 | (defun entry-abbreviation (entry) |
---|
331 | (second entry)) |
---|
332 | |
---|
333 | (defun entry-command (entry) |
---|
334 | (third entry)) |
---|
335 | |
---|
336 | (defun entry-help (entry) |
---|
337 | (fourth entry)) |
---|
338 | |
---|
339 | (defun find-command (string) |
---|
340 | (let ((len (length string))) |
---|
341 | (when (and (> len 0) |
---|
342 | (eql (schar string 0) *command-char*)) |
---|
343 | (setf string (subseq string 1) |
---|
344 | len (1- len))) |
---|
345 | (dolist (entry *command-table*) |
---|
346 | (when (or (string-equal string (entry-abbreviation entry)) |
---|
347 | (string-equal string (entry-name entry))) |
---|
348 | (return (entry-command entry)))))) |
---|
349 | |
---|
350 | (defun process-cmd (form) |
---|
351 | (when (eq form *null-cmd*) |
---|
352 | (return-from process-cmd t)) |
---|
353 | (when (and (stringp form) |
---|
354 | (> (length form) 1) |
---|
355 | (eql (char form 0) *command-char*)) |
---|
356 | (let* ((pos (or (position #\space form) |
---|
357 | (position #\return form))) |
---|
358 | (command-string (subseq form 0 pos)) |
---|
359 | (args (if pos (subseq form (1+ pos)) nil))) |
---|
360 | (let ((command (find-command command-string))) |
---|
361 | (cond ((null command) |
---|
362 | (sys::%format t "Unknown top-level command \"~A\".~%" command-string) |
---|
363 | (sys::%format t "Type \"~Ahelp\" for a list of available commands." *command-char*)) |
---|
364 | (t |
---|
365 | (when args |
---|
366 | (setf args (string-trim (list #\space #\return) args)) |
---|
367 | (when (zerop (length args)) |
---|
368 | (setf args nil))) |
---|
369 | (funcall command args))))) |
---|
370 | t)) |
---|
371 | |
---|
372 | (defun read-cmd (stream) |
---|
373 | (let ((c (peek-char-non-whitespace stream))) |
---|
374 | (cond ((eql c *command-char*) |
---|
375 | (let* ((input (read-line stream)) |
---|
376 | (name (symbol-name (read-from-string input)))) |
---|
377 | (if (find-command name) |
---|
378 | (progn (process-cmd input) *handled-cmd*) |
---|
379 | (read-from-string (concatenate 'string ":" name))))) |
---|
380 | ((eql c #\newline) |
---|
381 | (read-line stream) |
---|
382 | *null-cmd*) |
---|
383 | (t |
---|
384 | (read stream nil *null-cmd*))))) |
---|
385 | |
---|
386 | (defun repl-read-form-fun (in out) |
---|
387 | (loop |
---|
388 | (funcall *repl-prompt-fun* out) |
---|
389 | (finish-output out) |
---|
390 | (let ((form (read-cmd in))) |
---|
391 | (setf (charpos out) 0) |
---|
392 | (unless (eq form *null-cmd*) |
---|
393 | (incf *cmd-number*)) |
---|
394 | (cond ((or (eq form *null-cmd*) |
---|
395 | (eq form *handled-cmd*))) |
---|
396 | ((and (> *debug-level* 0) |
---|
397 | (fixnump form)) |
---|
398 | (let ((n form) |
---|
399 | (restarts (compute-restarts))) |
---|
400 | (if (< -1 n (length restarts)) |
---|
401 | (invoke-restart-interactively (nth n restarts)) |
---|
402 | (return form)))) |
---|
403 | (t |
---|
404 | (return form)))))) |
---|
405 | |
---|
406 | (defparameter *repl-read-form-fun* #'repl-read-form-fun) |
---|
407 | |
---|
408 | (defun repl (&optional (in *standard-input*) (out *standard-output*)) |
---|
409 | (loop |
---|
410 | (let* ((form (funcall *repl-read-form-fun* in out)) |
---|
411 | (results (multiple-value-list (sys:interactive-eval form)))) |
---|
412 | (dolist (result results) |
---|
413 | (fresh-line out) |
---|
414 | (prin1 result out))))) |
---|
415 | |
---|
416 | (defun top-level-loop () |
---|
417 | (fresh-line) |
---|
418 | (unless sys:*noinform* |
---|
419 | (sys::%format t "Type \"~Ahelp\" for a list of available commands.~%" *command-char*)) |
---|
420 | (loop |
---|
421 | (setf *inspected-object* nil |
---|
422 | *inspected-object-stack* nil |
---|
423 | *inspect-break* nil) |
---|
424 | (with-simple-restart (top-level |
---|
425 | "Return to top level.") |
---|
426 | (if (featurep :j) |
---|
427 | (handler-case |
---|
428 | (repl) |
---|
429 | (stream-error (c) (declare (ignore c)) (return-from top-level-loop))) |
---|
430 | (repl))))) |
---|