1#| repl.jl -- rep input loop
2
3   $Id: repl.jl,v 1.50 2004/10/07 05:03:54 jsh Exp $
4
5   Copyright (C) 2000 John Harper <john@dcs.warwick.ac.uk>
6
7   This file is part of librep.
8
9   librep is free software; you can redistribute it and/or modify it
10   under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2, or (at your option)
12   any later version.
13
14   librep is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19   You should have received a copy of the GNU General Public License
20   along with librep; see the file COPYING.  If not, write to
21   the Free Software Foundation, 51 Franklin Street, Fifth Floor,
22   Boston, MA 02110-1301 USA
23|#
24
25(define-structure rep.util.repl
26
27    (export repl
28	    make-repl
29	    repl-struct
30	    repl-pending
31	    repl-eval
32	    repl-iterate
33	    repl-completions
34	    define-repl-command)
35
36    (open rep
37	  rep.structures
38	  rep.system
39	  rep.regexp
40	  rep.io.files)
41
42  (define current-repl (make-fluid))
43
44  (define (make-repl #!optional initial-struct)
45    (cons (or initial-struct *user-structure*) nil))
46
47  (define repl-struct car)
48  (define repl-pending cdr)
49  (define repl-set-struct rplaca)
50  (define repl-set-pending rplacd)
51
52  (define (repl-eval form)
53    (eval form (intern-structure (repl-struct (fluid current-repl)))))
54
55  (define (repl-boundp x)
56    (condition-case nil
57	(progn
58	  (repl-eval x)
59	  t)
60      (void-value nil)))
61
62  ;; returns t if repl should run again
63  (define (repl-iterate repl input)
64    (setq input (concat (repl-pending repl) input))
65    (repl-set-pending repl nil)
66    (let-fluids ((current-repl repl))
67      (let ((print-escape t))
68	(catch 'return
69	  (condition-case data
70	      (progn
71		(cond
72		 ((string-looking-at "\\s*,\\s*" input)
73		  ;; a `,' introduces a meta command
74		  (let ((stream (make-string-input-stream input (match-end)))
75			(sexps '()))
76		    (condition-case nil
77			(while t
78			  (setq sexps (cons (read stream) sexps)))
79		      (end-of-stream (setq sexps (nreverse sexps))))
80		    (let ((command (repl-command (car sexps))))
81		      (and command (apply command (cdr sexps))))))
82
83		 ;; ignore empty input lines, or lines with comments only
84		 ((string-looking-at "\\s*(;.*)?$" input))
85
86		 (t (let ((form (condition-case nil
87				    (read-from-string input)
88				  (premature-end-of-stream
89				   (repl-set-pending repl input)
90				   (throw 'return
91					  (and input
92					       (not (string= "" input))))))))
93		      (let ((result (repl-eval form)))
94			(unless (eq result #undefined)
95			  (format standard-output "%S\n" result))))))
96		t)
97	    (error
98	     (default-error-handler (car data) (cdr data))
99	     t))))))
100
101  (define (do-readline prompt completer)
102    (if (file-ttyp standard-input)
103	(progn
104	  (require 'rep.io.readline)
105	  (readline prompt completer))
106      (write standard-output prompt)
107      (read-line standard-input)))
108
109  (define (repl #!optional initial-structure)
110    ;; returns t if repl should run again
111    (define (run-repl)
112      (let ((input (do-readline
113		    (format nil (if (repl-pending (fluid current-repl))
114				    "" "%s> ")
115			    (repl-struct (fluid current-repl)))
116		    completion-generator)))
117	(and input (repl-iterate (fluid current-repl) input))))
118    (define (interrupt-handler data)
119      (if (eq (car data) 'user-interrupt)
120	  (progn
121	    (format standard-output "User interrupt!\n")
122	    t)
123	(raise-exception data)))
124    (let-fluids ((current-repl (make-repl initial-structure)))
125      (write standard-output "\nEnter `,help' to list commands.\n")
126      (let loop ()
127	(when (call-with-exception-handler run-repl interrupt-handler)
128	  (loop)))))
129
130  (define (print-list data #!optional map)
131    (unless map (setq map identity))
132    (let* ((count (length data))
133	   (mid (inexact->exact (ceiling (/ count 2)))))
134      (do ((i 0 (1+ i))
135	   (left data (cdr left))
136	   (right (nthcdr mid data) (cdr right)))
137	  ((null left))
138	(when (< i mid)
139	  (format standard-output "  %-30s"
140		  (format nil "%s" (map (car left))))
141	  (when right
142	    (format standard-output " %s" (map (car right))))
143	  (write standard-output #\newline)))))
144
145  (define (completion-generator w)
146    ;; Either a special command or unquote.
147    (if (string-head-eq w ",")
148	(mapcar (lambda (x) (concat "," (symbol-name x)))
149		(apropos (concat #\^ (quote-regexp (substring w 1)))
150			 (lambda (x) (assq x repl-commands))))
151      (apropos (concat #\^ (quote-regexp w)) repl-boundp)))
152
153  (define (repl-completions repl word)
154    (let-fluids ((current-repl repl))
155      (completion-generator word)))
156
157
158;;; module utils
159
160  (define (module-exports-p name var)
161    (structure-exports-p (get-structure name) var))
162
163  (define (module-imports name)
164     (structure-imports (get-structure name)))
165
166  (define (locate-binding* name)
167    (or (locate-binding name (append (list (repl-struct (fluid current-repl)))
168				     (module-imports
169				      (repl-struct (fluid current-repl)))))
170	(and (structure-bound-p
171	      (get-structure (repl-struct (fluid current-repl))) name)
172	     (repl-struct (fluid current-repl)))))
173
174
175;;; commands
176
177  (define repl-commands '())
178
179  (define (define-repl-command name function #!optional doc)
180    (let ((cell (assq name repl-commands)))
181      (if cell
182	  (rplacd cell (list function doc))
183	(setq repl-commands (cons (list name function doc) repl-commands)))))
184
185  (define (find-command name)
186    (let ((cell (assq name repl-commands)))
187      (if cell
188	  cell
189	;; look for an unambiguous match
190	(let ((re (concat "^" (quote-regexp (symbol-name name)))))
191	  (let loop ((rest repl-commands)
192		     (matched nil))
193	    (cond ((null rest)
194		   (if matched
195		       matched
196		     (format standard-output "unknown command: ,%s\n" name)
197		     nil))
198		  ((string-match re (symbol-name (caar rest)))
199		   (if matched
200		       ;; already saw something, exit
201		       (progn
202			 (format standard-output
203				 "non-unique abbreviation: ,%s\n" name)
204			 nil)
205		     (loop (cdr rest) (car rest))))
206		  (t (loop (cdr rest) matched))))))))
207
208  (define (repl-command name)
209    (let ((cell (find-command name)))
210      (and cell (cadr cell))))
211
212  (define (repl-documentation name)
213    (let ((cell (find-command name)))
214      (and cell (caddr cell))))
215
216  (define-repl-command
217   'in
218   (lambda (struct #!optional form)
219     (if form
220	 (format standard-output "%S\n"
221		 (eval form (get-structure struct)))
222       (repl-set-struct (fluid current-repl) struct)))
223   "STRUCT [FORM ...]")
224
225  (define-repl-command
226   'load
227   (lambda structs
228     (mapc (lambda (struct)
229	     (intern-structure struct)) structs))
230   "STRUCT ...")
231
232  (define-repl-command
233   'reload
234   (lambda structs
235     (mapc (lambda (x)
236	     (let ((struct (get-structure x)))
237	       (when struct
238		 (name-structure struct nil))
239	       (intern-structure x))) structs))
240   "STRUCT ...")
241
242  (define-repl-command
243   'unload
244   (lambda structs
245     (mapc (lambda (x)
246	     (let ((struct (get-structure x)))
247	       (when struct
248		 (name-structure struct nil)))) structs))
249   "STRUCT ...")
250
251  (define-repl-command
252   'load-file
253   (lambda files
254     (mapc (lambda (f)
255	     (repl-eval `(,load ,f))) files))
256   "\"FILENAME\" ...")
257
258  (define-repl-command
259   'open
260   (lambda structs
261     (repl-eval `(,open-structures (,quote ,structs))))
262   "STRUCT ...")
263
264  (define-repl-command
265   'access
266   (lambda structs
267     (repl-eval `(,access-structures (,quote ,structs))))
268   "STRUCT ...")
269
270  (define-repl-command
271   'structures
272   (lambda ()
273     (let (structures)
274       (structure-walk (lambda (var value)
275			 (declare (unused value))
276			 (when value
277			   (setq structures (cons var structures))))
278		       (get-structure '%structures))
279       (print-list (sort structures)))))
280
281  (define-repl-command
282   'interfaces
283   (lambda ()
284     (let (interfaces)
285       (structure-walk (lambda (var value)
286			 (declare (unused value))
287			 (setq interfaces (cons var interfaces)))
288		       (get-structure '%interfaces))
289       (print-list (sort interfaces)))))
290
291  (define-repl-command
292   'bindings
293   (lambda ()
294     (structure-walk (lambda (var value)
295		       (format standard-output "  (%s %S)\n" var value))
296		     (intern-structure
297		      (repl-struct (fluid current-repl))))))
298
299  (define-repl-command
300   'exports
301   (lambda ()
302     (print-list (structure-interface
303		  (intern-structure
304		   (repl-struct (fluid current-repl)))))))
305
306  (define-repl-command
307   'imports
308   (lambda ()
309     (print-list (module-imports (repl-struct (fluid current-repl))))))
310
311  (define-repl-command
312   'accessible
313   (lambda ()
314     (print-list (structure-accessible
315		  (intern-structure
316		   (repl-struct (fluid current-repl)))))))
317
318  (define-repl-command
319   'collect
320   (lambda ()
321     (let ((stats (garbage-collect t)))
322       (format standard-output "Used %d/%d cons, %d/%d tuples, %d strings, %d vector slots, %d/%d closures\n"
323	       (car (nth 0 stats)) (+ (car (nth 0 stats)) (cdr (nth 0 stats)))
324	       (car (nth 1 stats)) (+ (car (nth 1 stats)) (cdr (nth 1 stats)))
325	       (car (nth 2 stats))
326	       (nth 3 stats)
327	       (car (nth 4 stats)) (+ (car (nth 4 stats))
328				      (cdr (nth 4 stats)))))))
329
330  (define-repl-command
331   'disassemble
332   (lambda (arg)
333     (require 'rep.vm.disassembler)
334     (disassemble (repl-eval arg)))
335   "FORM")
336
337  (define-repl-command
338   'compile-proc
339   (lambda args
340     (require 'rep.vm.compiler)
341     (mapc (lambda (arg)
342	     (compile-function (repl-eval arg) arg)) args))
343   "PROCEDURE ...")
344
345  (define-repl-command
346   'compile
347   (lambda args
348     (require 'rep.vm.compiler)
349     (if (null args)
350	 (compile-module (repl-struct (fluid current-repl)))
351       (mapc compile-module args)))
352   "[STRUCT ...]")
353
354  (define-repl-command
355   'compile-file
356   (lambda args
357     (require 'rep.vm.compiler)
358     (mapc compile-file args))
359   "\"FILENAME\" ...")
360
361  (define-repl-command
362   'new
363   (lambda (name)
364     (declare (bound %open-structures))
365     (make-structure nil (lambda ()
366			   (%open-structures '(rep.module-system)))
367		     nil name)
368     (repl-set-struct (fluid current-repl) name))
369   "STRUCT")
370
371  (define-repl-command
372   'expand
373   (lambda (form)
374     (format standard-output "%s\n" (repl-eval `(,macroexpand ',form))))
375   "FORM")
376
377  (define-repl-command
378   'step
379   (lambda (form)
380     (format standard-output "%s\n" (repl-eval `(,step ',form))))
381   "FORM")
382
383  (define-repl-command
384   'help
385   (lambda ()
386     (write standard-output "
387Either enter lisp forms to be evaluated, and their result printed, or
388enter a meta-command prefixed by a `,' character. Names of meta-
389commands may be abbreviated to their unique leading characters.\n\n")
390     (print-list (sort (mapcar car repl-commands))
391		 (lambda (x)
392		   (format nil ",%s %s" x (or (repl-documentation x) ""))))))
393
394  (define-repl-command 'quit (lambda () (throw 'quit 0)))
395
396  (define-repl-command
397   'describe
398   (lambda (name)
399     (require 'rep.lang.doc)
400     (let* ((value (repl-eval name))
401	    (struct (locate-binding* name))
402	    (doc (documentation name struct value)))
403       (write standard-output #\newline)
404       (describe-value value name struct)
405       (write standard-output #\newline)
406       (when doc
407	 (format standard-output "%s\n\n" doc))))
408   "SYMBOL")
409
410  (define-repl-command
411   'apropos
412   (lambda (re)
413     (require 'rep.lang.doc)
414     (let ((funs (apropos re repl-boundp)))
415       (mapc (lambda (x)
416	       (describe-value (repl-eval x) x)) funs)))
417   "\"REGEXP\"")
418
419  (define-repl-command
420   'locate
421   (lambda (var)
422     (let ((struct (locate-binding* var)))
423       (if struct
424	   (format standard-output "%s is bound in: %s.\n" var struct)
425	 (format standard-output "%s is unbound.\n" var))))
426   "SYMBOL")
427
428  (define-repl-command
429   'whereis
430   (lambda (var)
431     (let ((out '()))
432       (structure-walk (lambda (k v)
433			 (declare (unused k))
434			 (when (and v (structure-name v)
435				    (structure-exports-p v var))
436			   (setq out (cons (structure-name v) out))))
437		       (get-structure '%structures))
438       (if out
439	   (format standard-output "%s is exported by: %s.\n"
440		   var (mapconcat symbol-name (sort out) ", "))
441	 (format standard-output "No module exports %s.\n" var))))
442   "SYMBOL")
443
444  (define-repl-command
445   'time
446   (lambda (form)
447     (let (t1 t2 ret)
448       (setq t1 (current-utime))
449       (setq ret (repl-eval form))
450       (setq t2 (current-utime))
451       (format standard-output
452	       "%S\nElapsed: %d seconds\n" ret (/ (- t2 t1) 1e6))))
453   "FORM")
454
455  (define-repl-command
456   'profile
457   (lambda (form)
458     (require 'rep.lang.profiler)
459     (format standard-output "%S\n\n" (call-in-profiler
460				       (lambda () (repl-eval form))))
461     (print-profile))
462   "FORM")
463
464  (define-repl-command
465   'check
466   (lambda (#!optional module)
467     (require 'rep.test.framework)
468     (if (null module)
469	 (run-all-self-tests)
470       (run-module-self-tests module)))
471   "[STRUCT]"))
472