cleanups to value and help help handlers
[bpt/guile.git] / ice-9 / session.scm
CommitLineData
cd5fea8d 1;;;; Copyright (C) 1997, 2000, 2001, 2003, 2006 Free Software Foundation, Inc.
7bb1bfc2 2;;;;
73be1d9e
MV
3;;;; This library is free software; you can redistribute it and/or
4;;;; modify it under the terms of the GNU Lesser General Public
5;;;; License as published by the Free Software Foundation; either
6;;;; version 2.1 of the License, or (at your option) any later version.
7;;;;
8;;;; This library is distributed in the hope that it will be useful,
0e81dabd 9;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
73be1d9e
MV
10;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11;;;; Lesser General Public License for more details.
12;;;;
13;;;; You should have received a copy of the GNU Lesser General Public
14;;;; License along with this library; if not, write to the Free Software
92205699 15;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
a482f2cc 16;;;;
0e81dabd
MD
17\f
18
bbefd480 19(define-module (ice-9 session)
3bdca000 20 :use-module (ice-9 documentation)
3510b484 21 :use-module (ice-9 regex)
1a179b03 22 :use-module (ice-9 rdelim)
4f7a0504
AW
23 :export (help
24 add-value-help-handler! remove-value-help-handler!
25 add-name-help-handler! remove-name-help-handler!
26 apropos apropos-internal apropos-fold apropos-fold-accessible
27 apropos-fold-exported apropos-fold-all source arity
28 system-module module-commentary))
0e81dabd
MD
29
30\f
31
53d81399
AW
32(define *value-help-handlers*
33 `(,(lambda (name value)
34 (object-documentation value))))
4f7a0504
AW
35
36(define (add-value-help-handler! proc)
37 "Adds a handler for performing `help' on a value.
38
39`proc' will be called as (PROC NAME VALUE). `proc' should return #t to
40indicate that it has performed help, a string to override the default
41object documentation, or #f to try the other handlers, potentially
42falling back on the normal behavior for `help'."
43 (set! *value-help-handlers* (cons proc *value-help-handlers*)))
44
45(define (remove-value-help-handler! proc)
53d81399 46 "Removes a handler for performing `help' on a value."
4f7a0504
AW
47 (set! *value-help-handlers* (delete! proc *value-help-handlers*)))
48
49(define (try-value-help name value)
50 (or-map (lambda (proc) (proc name value)) *value-help-handlers*))
51
52
53(define *name-help-handlers* '())
54
55(define (add-name-help-handler! proc)
56 "Adds a handler for performing `help' on a name.
57
58`proc' will be called with the unevaluated name as its argument. That is
59to say, when the user calls `(help FOO)', the name is FOO, exactly as
60the user types it.
61
53d81399
AW
62`proc' should return #t to indicate that it has performed help, a string
63to override the default object documentation, or #f to try the other
64handlers, potentially falling back on the normal behavior for `help'."
4f7a0504
AW
65 (set! *name-help-handlers* (cons proc *name-help-handlers*)))
66
67(define (remove-name-help-handler! proc)
53d81399 68 "Removes a handler for performing `help' on a name."
4f7a0504
AW
69 (set! *name-help-handlers* (delete! proc *name-help-handlers*)))
70
71(define (try-name-help name)
72 (or-map (lambda (proc) (proc name)) *name-help-handlers*))
73
74
13ae9151
MD
75;;; Documentation
76;;;
1a179b03 77(define help
13ae9151
MD
78 (procedure->syntax
79 (lambda (exp env)
80 "(help [NAME])
81Prints useful information. Try `(help)'."
9f0eee46 82 (cond ((not (= (length exp) 2))
de25f281
TTN
83 (help-usage))
84 ((not (provided? 'regex))
85 (display "`help' depends on the `regex' feature.
9f0eee46 86You don't seem to have regular expressions installed.\n"))
de25f281
TTN
87 (else
88 (let ((name (cadr exp))
89 (not-found (lambda (type x)
90 (simple-format #t "No ~A found for ~A\n"
91 type x))))
92 (cond
93
4f7a0504
AW
94 ;; User-specified
95 ((try-name-help name)
96 => (lambda (x) (if (not (eq? x #t)) (display x))))
97
de25f281
TTN
98 ;; SYMBOL
99 ((symbol? name)
100 (help-doc name
101 (simple-format
102 #f "^~A$"
103 (regexp-quote (symbol->string name)))))
104
105 ;; "STRING"
106 ((string? name)
107 (help-doc name name))
108
109 ;; (unquote SYMBOL)
110 ((and (list? name)
111 (= (length name) 2)
112 (eq? (car name) 'unquote))
53d81399
AW
113 (let ((doc (try-value-help (cadr name)
114 (local-eval (cadr name) env))))
115 (cond ((not doc) (not-found 'documentation (cadr name)))
116 ((eq? doc #t)) ;; pass
117 (else (write-line doc)))))
de25f281
TTN
118
119 ;; (quote SYMBOL)
120 ((and (list? name)
121 (= (length name) 2)
122 (eq? (car name) 'quote)
123 (symbol? (cadr name)))
124 (cond ((search-documentation-files (cadr name))
125 => write-line)
126 (else (not-found 'documentation (cadr name)))))
127
128 ;; (SYM1 SYM2 ...)
129 ((and (list? name)
130 (and-map symbol? name)
131 (not (null? name))
132 (not (eq? (car name) 'quote)))
133 (cond ((module-commentary name)
134 => (lambda (doc)
135 (display name) (write-line " commentary:")
136 (write-line doc)))
137 (else (not-found 'commentary name))))
138
139 ;; unrecognized
140 (else
141 (help-usage)))
142 *unspecified*))))))
13ae9151 143
7bb1bfc2
TTN
144(define (module-filename name) ; fixme: better way? / done elsewhere?
145 (let* ((name (map symbol->string name))
146 (reverse-name (reverse name))
147 (leaf (car reverse-name))
148 (dir-hint-module-name (reverse (cdr reverse-name)))
149 (dir-hint (apply string-append
150 (map (lambda (elt)
151 (string-append elt "/"))
152 dir-hint-module-name))))
153 (%search-load-path (in-vicinity dir-hint leaf))))
154
155(define (module-commentary name)
156 (cond ((module-filename name) => file-commentary)
157 (else #f)))
158
3bdca000
MD
159(define (help-doc term regexp)
160 (let ((entries (apropos-fold (lambda (module name object data)
161 (cons (list module
162 name
53d81399 163 (try-value-help name object)
db611983
NJ
164 (cond ((closure? object)
165 "a procedure")
166 ((procedure? object)
167 "a primitive procedure")
168 (else
169 "an object")))
3bdca000
MD
170 data))
171 '()
172 regexp
173 apropos-fold-exported))
174 (module car)
175 (name cadr)
db611983
NJ
176 (doc caddr)
177 (type cadddr))
de25f281
TTN
178 (cond ((not (null? entries))
179 (let ((first? #t)
180 (undocumented-entries '())
181 (documented-entries '())
182 (documentations '()))
183
184 (for-each (lambda (entry)
185 (let ((entry-summary (simple-format
186 #f "~S: ~S\n"
187 (module-name (module entry))
188 (name entry))))
189 (if (doc entry)
190 (begin
191 (set! documented-entries
192 (cons entry-summary documented-entries))
193 ;; *fixme*: Use `describe' when we have GOOPS?
194 (set! documentations
195 (cons (simple-format
196 #f "`~S' is ~A in the ~S module.\n\n~A\n"
197 (name entry)
198 (type entry)
199 (module-name (module entry))
200 (doc entry))
201 documentations)))
202 (set! undocumented-entries
203 (cons entry-summary
204 undocumented-entries)))))
205 entries)
206
207 (if (and (not (null? documented-entries))
208 (or (> (length documented-entries) 1)
209 (not (null? undocumented-entries))))
210 (begin
211 (display "Documentation found for:\n")
212 (for-each (lambda (entry) (display entry))
213 documented-entries)
214 (set! first? #f)))
215
216 (for-each (lambda (entry)
217 (if first?
218 (set! first? #f)
219 (newline))
220 (display entry))
221 documentations)
222
223 (if (not (null? undocumented-entries))
224 (begin
225 (if first?
226 (set! first? #f)
227 (newline))
228 (display "No documentation found for:\n")
229 (for-each (lambda (entry) (display entry))
230 undocumented-entries)))))
231 ((search-documentation-files term)
232 => (lambda (doc)
233 (write-line "Documentation from file:")
234 (write-line doc)))
235 (else
236 ;; no matches
237 (display "Did not find any object ")
238 (simple-format #t
239 (if (symbol? term)
240 "named `~A'\n"
241 "matching regexp \"~A\"\n")
242 term)))))
3bdca000 243
13ae9151 244(define (help-usage)
3bdca000
MD
245 (display "Usage: (help NAME) gives documentation about objects named NAME (a symbol)
246 (help REGEXP) ditto for objects with names matching REGEXP (a string)
1de3b33b 247 (help 'NAME) gives documentation for NAME, even if it is not an object
d1c50f73 248 (help ,EXPR) gives documentation for object returned by EXPR
8bbe4c82 249 (help (my module)) gives module commentary for `(my module)'
13ae9151
MD
250 (help) gives this text
251
3bdca000
MD
252`help' searches among bindings exported from loaded modules, while
253`apropos' searches among bindings visible from the \"current\" module.
254
2f52380c
MD
255Examples: (help help)
256 (help cons)
3bdca000 257 (help \"output-string\")
13ae9151
MD
258
259Other useful sources of helpful information:
260
261(apropos STRING)
262(arity PROCEDURE)
263(name PROCEDURE-OR-MACRO)
264(source PROCEDURE-OR-MACRO)
265
266Tools:
267
2f52380c
MD
268(backtrace) ;show backtrace from last error
269(debug) ;enter the debugger
270(trace [PROCEDURE]) ;trace procedure (no arg => show)
271(untrace [PROCEDURE]) ;untrace (no arg => untrace all)
13ae9151
MD
272
273(OPTIONSET-options 'full) ;display option information
274(OPTIONSET-enable 'OPTION)
275(OPTIONSET-disable 'OPTION)
276(OPTIONSET-set! OPTION VALUE)
277
278where OPTIONSET is one of debug, read, eval, print
279
280"))
281
0e81dabd
MD
282;;; {Apropos}
283;;;
284;;; Author: Roland Orre <orre@nada.kth.se>
285;;;
286
1a179b03 287(define (apropos rgx . options)
0e81dabd
MD
288 "Search for bindings: apropos regexp {options= 'full 'shadow 'value}"
289 (if (zero? (string-length rgx))
290 "Empty string not allowed"
4f161c5c 291 (let* ((match (make-regexp rgx))
3742da68 292 (uses (module-uses (current-module)))
0e81dabd 293 (modules (cons (current-module)
3742da68
MD
294 (if (and (not (null? uses))
295 (eq? (module-name (car uses))
296 'duplicates))
297 (cdr uses)
298 uses)))
0e81dabd
MD
299 (separator #\tab)
300 (shadow (member 'shadow options))
301 (value (member 'value options)))
302 (cond ((member 'full options)
303 (set! shadow #t)
304 (set! value #t)))
305 (for-each
306 (lambda (module)
296ff5e7
MV
307 (let* ((name (module-name module))
308 (obarray (module-obarray module)))
309 ;; XXX - should use hash-fold here
1798b73d
MD
310 (hash-for-each
311 (lambda (symbol variable)
312 (cond ((regexp-exec match (symbol->string symbol))
313 (display name)
314 (display ": ")
315 (display symbol)
316 (cond ((variable-bound? variable)
317 (let ((val (variable-ref variable)))
318 (cond ((or (procedure? val) value)
319 (display separator)
320 (display val)))))
321 (else
322 (display separator)
323 (display "(unbound)")))
324 (if (and shadow
325 (not (eq? (module-ref module symbol)
326 (module-ref (current-module) symbol))))
327 (display " shadowed"))
328 (newline))))
10764e3c 329 obarray)))
0e81dabd 330 modules))))
68aed3ea 331
1a179b03 332(define (apropos-internal rgx)
68aed3ea 333 "Return a list of accessible variable names."
3bdca000
MD
334 (apropos-fold (lambda (module name var data)
335 (cons name data))
336 '()
337 rgx
338 (apropos-fold-accessible (current-module))))
339
1a179b03 340(define (apropos-fold proc init rgx folder)
3bdca000
MD
341 "Folds PROCEDURE over bindings matching third arg REGEXP.
342
343Result is
344
345 (PROCEDURE MODULE1 NAME1 VALUE1
346 (PROCEDURE MODULE2 NAME2 VALUE2
347 ...
348 (PROCEDURE MODULEn NAMEn VALUEn INIT)))
349
350where INIT is the second arg to `apropos-fold'.
351
352Fourth arg FOLDER is one of
353
354 (apropos-fold-accessible MODULE) ;fold over bindings accessible in MODULE
355 apropos-fold-exported ;fold over all exported bindings
356 apropos-fold-all ;fold over all bindings"
357 (let ((match (make-regexp rgx))
358 (recorded (make-vector 61 '())))
359 (let ((fold-module
360 (lambda (module data)
361 (let* ((obarray-filter
362 (lambda (name val data)
4adc3028 363 (if (and (regexp-exec match (symbol->string name))
3bdca000
MD
364 (not (hashq-get-handle recorded name)))
365 (begin
366 (hashq-set! recorded name #t)
367 (proc module name val data))
368 data)))
369 (module-filter
370 (lambda (name var data)
aef9dd65
MV
371 (if (variable-bound? var)
372 (obarray-filter name (variable-ref var) data)
373 data))))
296ff5e7 374 (cond (module (hash-fold module-filter
3bdca000
MD
375 data
376 (module-obarray module)))
377 (else data))))))
378 (folder fold-module init))))
379
380(define (make-fold-modules init-thunk traverse extract)
381 "Return procedure capable of traversing a forest of modules.
382The forest traversed is the image of the forest generated by root
383modules returned by INIT-THUNK and the generator TRAVERSE.
384It is an image under the mapping EXTRACT."
385 (lambda (fold-module init)
9aec4751
MD
386 (let* ((table (make-hash-table 31))
387 (first? (lambda (obj)
8d627248
MD
388 (let* ((handle (hash-create-handle! table obj #t))
389 (first? (cdr handle)))
390 (set-cdr! handle #f)
391 first?))))
9aec4751
MD
392 (let rec ((data init)
393 (modules (init-thunk)))
394 (do ((modules modules (cdr modules))
395 (data data (if (first? (car modules))
396 (rec (fold-module (extract (car modules)) data)
397 (traverse (car modules)))
398 data)))
399 ((null? modules) data))))))
3bdca000 400
1a179b03 401(define (apropos-fold-accessible module)
3bdca000
MD
402 (make-fold-modules (lambda () (list module))
403 module-uses
de25f281 404 identity))
3bdca000
MD
405
406(define (root-modules)
407 (cons the-root-module
408 (submodules (nested-ref the-root-module '(app modules)))))
409
410(define (submodules m)
411 (hash-fold (lambda (name var data)
aef9dd65 412 (let ((obj (and (variable-bound? var) (variable-ref var))))
3bdca000
MD
413 (if (and (module? obj)
414 (eq? (module-kind obj) 'directory))
415 (cons obj data)
416 data)))
417 '()
418 (module-obarray m)))
419
1a179b03 420(define apropos-fold-exported
3bdca000
MD
421 (make-fold-modules root-modules submodules module-public-interface))
422
1a179b03 423(define apropos-fold-all
de25f281 424 (make-fold-modules root-modules submodules identity))
7cfae7e6 425
1a179b03 426(define (source obj)
7cfae7e6
MD
427 (cond ((procedure? obj) (procedure-source obj))
428 ((macro? obj) (procedure-source (macro-transformer obj)))
429 (else #f)))
4a9f464e 430
1a179b03 431(define (arity obj)
c7bb434f
TTN
432 (define (display-arg-list arg-list)
433 (display #\`)
434 (display (car arg-list))
435 (let loop ((ls (cdr arg-list)))
436 (cond ((null? ls)
437 (display #\'))
438 ((not (pair? ls))
439 (display "', the rest in `")
440 (display ls)
441 (display #\'))
442 (else
443 (if (pair? (cdr ls))
444 (display "', `")
445 (display "' and `"))
446 (display (car ls))
447 (loop (cdr ls))))))
448 (define (display-arg-list/summary arg-list type)
449 (let ((len (length arg-list)))
450 (display len)
451 (display " ")
452 (display type)
453 (if (> len 1)
454 (display " arguments: ")
455 (display " argument: "))
456 (display-arg-list arg-list)))
457 (cond
458 ((procedure-property obj 'arglist)
459 => (lambda (arglist)
460 (let ((required-args (car arglist))
461 (optional-args (cadr arglist))
462 (keyword-args (caddr arglist))
463 (allow-other-keys? (cadddr arglist))
464 (rest-arg (car (cddddr arglist)))
465 (need-punctuation #f))
466 (cond ((not (null? required-args))
467 (display-arg-list/summary required-args "required")
468 (set! need-punctuation #t)))
469 (cond ((not (null? optional-args))
470 (if need-punctuation (display ", "))
471 (display-arg-list/summary optional-args "optional")
472 (set! need-punctuation #t)))
473 (cond ((not (null? keyword-args))
474 (if need-punctuation (display ", "))
475 (display-arg-list/summary keyword-args "keyword")
476 (set! need-punctuation #t)))
477 (cond (allow-other-keys?
478 (if need-punctuation (display ", "))
479 (display "other keywords allowed")
480 (set! need-punctuation #t)))
481 (cond (rest-arg
482 (if need-punctuation (display ", "))
483 (display "the rest in `")
484 (display rest-arg)
485 (display "'"))))))
486 (else
487 (let ((arity (procedure-property obj 'arity)))
488 (display (car arity))
489 (cond ((caddr arity)
490 (display " or more"))
491 ((not (zero? (cadr arity)))
492 (display " required and ")
493 (display (cadr arity))
494 (display " optional")))
495 (if (and (not (caddr arity))
496 (= (car arity) 1)
497 (<= (cadr arity) 1))
498 (display " argument")
499 (display " arguments"))
500 (if (closure? obj)
501 (let ((formals (cadr (procedure-source obj))))
502 (cond
503 ((pair? formals)
504 (display ": ")
505 (display-arg-list formals))
506 (else
507 (display " in `")
508 (display formals)
509 (display #\'))))))))
510 (display ".\n"))
6ae34994 511
1a179b03 512(define system-module
6ae34994
MD
513 (procedure->syntax
514 (lambda (exp env)
515 (let* ((m (nested-ref the-root-module
516 (append '(app modules) (cadr exp)))))
517 (if (not m)
518 (error "Couldn't find any module named" (cadr exp)))
519 (let ((s (not (procedure-property (module-eval-closure m)
520 'system-module))))
521 (set-system-module! m s)
522 (string-append "Module " (symbol->string (module-name m))
523 " is now a " (if s "system" "user") " module."))))))
de25f281
TTN
524
525;;; session.scm ends here