Initial Commit
[packages] / xemacs-packages / ilisp / guile-ilisp.scm
1 ;;;; guile-ilisp.scm --- ILISP support functions for GUILE
2 ;;;; Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de> 
3 ;;;
4 ;;; Copyright (C) 2000 Matthias Koeppe
5 ;;;
6 ;;; This file is part of ILISP.
7 ;;; Please refer to the file COPYING for copyrights and licensing
8 ;;; information.
9 ;;; Please refer to the file ACKNOWLEGDEMENTS for an (incomplete) list
10 ;;; of present and past contributors.
11 ;;;
12 ;;; $Id: guile-ilisp.scm,v 1.2 2002-06-03 23:36:58 wbd Exp $
13
14 \f
15 (define-module (guile-ilisp)
16   :use-module (ice-9 debug)
17   :use-module (ice-9 session)
18   :use-module (ice-9 documentation)
19   :use-module (ice-9 regex))
20
21 (define-module (guile-user)
22   :use-module (guile-ilisp))
23
24 (define-module (guile-ilisp))
25
26 (define (read-from-string str)
27   (call-with-input-string str read))
28
29 (define (read-from-string/source str filename line column)
30   "Read from string STR, pretending the source is the given FILENAME, LINE, COLUMN."
31   (call-with-input-string
32    str
33    (lambda (port)
34      (set-port-filename! port filename)
35      (set-port-line! port (- line 1))
36      (set-port-column! port (- column 1))
37      (read port))))
38
39 (define (string->module str)
40   (let ((v (call-with-input-string str read)))
41     (cond
42      ((eq? 'nil v) (current-module))
43      ((pair? v) (resolve-module v))
44      (else (resolve-module (list v))))))
45
46 (define (first-line s)
47   (let ((i (string-index s #\newline)))
48     (if i
49         (substring s 0 i)
50         s)))
51
52 (define (doc->arglist doc with-procedure?)
53   "Parse DOC to find the arglist and return it as a string.  If
54 WITH-PROCEDURE?, include the procedure symbol."
55   (let ((pattern " - primitive: "))
56     (cond
57      ((string=? (substring doc 0 (string-length pattern))
58                 pattern)
59       ;; Guile 1.4.1 primitive procedure documentation, passed through
60       ;; TeXinfo:
61       ;;
62       ;;  - primitive: assoc key alist
63       ;;     Behaves like `assq' but uses `equal?' for key comparison.
64       ;;
65       ;; Continuation lines of arglists have an indentation of 10 chars.
66       (let ((start-index
67              (if with-procedure?
68                  (string-length pattern)
69                  (min (1+ (or (string-index doc #\space
70                                             (string-length pattern))
71                               (string-length pattern)))
72                       (or (string-index doc #\newline
73                                         (string-length pattern))
74                           (string-length pattern))))))
75         (let ((eol-index (or (string-index doc #\newline start-index)
76                              (string-length doc))))
77           (string-append 
78            "("
79            (let loop ((bol-index (+ 1 eol-index))
80                       (arglist (substring doc start-index eol-index)))
81              (cond 
82               ((and bol-index (>= bol-index (string-length doc)))
83                arglist)
84               ((and (>= (string-length doc) (+ bol-index 10))
85                     (string=? (substring doc bol-index (+ bol-index 10))
86                               "          "))
87                (let ((eol-index (string-index doc #\newline bol-index)))
88                  (loop (and eol-index (+ 1 eol-index))
89                        (string-append arglist " " 
90                                       (substring doc (+ bol-index 10)
91                                                  eol-index)))))
92               (else
93                arglist)))
94            ")"))))
95      ((string=? (substring doc 0 1) "(")
96       ;; Guile <= 1.4 primitive procedure documentation and other
97       ;; conventions:
98       ;;
99       ;; (help [NAME])
100       ;; Prints useful information.  Try `(help)'.
101       ;;
102       (if with-procedure?
103           (first-line doc)
104           (let* ((f-l (first-line doc))
105                  (index (string-index f-l #\space)))
106             (if index
107                 (string-append "("
108                                (substring f-l
109                                           (+ index 1)))
110                 "()"))))     
111      (else (string-append "CAN'T PARSE THIS DOCUMENTATION:\n"
112                           doc)))))
113
114 (define (info-message sym obj expensive? arglist-only?)
115   "Return an informational message about OBJ, which is the value of SYM.
116 For procedures, return procedure symbol and arglist, or
117 fall back to a message on the arity; if ARGLIST-ONLY?, return the
118 arglist only.  If EXPENSIVE?, take some more effort."
119   ;; The code here is so lengthy because we want to return a
120   ;; meaningful result even if we aren't allowed to read the
121   ;; documentation files (EXPENSIVE? = #f).
122     (cond
123      ((and (procedure? obj)
124            (procedure-property obj 'arglist))
125       => (lambda (arglist)
126            (let ((required-args (car arglist))
127                  (optional-args (cadr arglist))
128                  (keyword-args (caddr arglist))
129                  (allow-other-keys? (cadddr arglist))
130                  (rest-arg (car (cddddr arglist))))
131              (with-output-to-string
132                (lambda ()
133                  (define (arg-only arg/default)
134                    (if (pair? arg/default) (car arg/default) arg/default))
135                  (write
136                   (append
137                    (if arglist-only?
138                        '()
139                        (list sym))
140                    required-args
141                    (if (not (null? optional-args))
142                        (cons #:optional (map arg-only optional-args))
143                        '())
144                    (if (not (null? keyword-args))
145                        (cons #:key (map arg-only keyword-args))
146                        '())
147                    (if allow-other-keys?
148                        (list #:allow-other-keys)
149                        '())
150                    (if rest-arg rest-arg '()))))))))
151      ((closure? obj)
152       (let ((formals (cadr (procedure-source obj))))
153         (if arglist-only? formals (cons sym formals))))
154      ((or
155        (and expensive?
156             (false-if-exception
157              ;; object-documentation was introduced in Guile 1.4,
158              ;; There is no documentation for primitives in earlier
159              ;; versions.
160              (object-documentation obj)))
161        (and (procedure? obj)
162             (procedure-property obj 'documentation)
163             ;; The documentation property is attached to a primitive
164             ;; procedure when it was read from the documentation file
165             ;; before.
166             ))
167       => (lambda (doc)
168            (doc->arglist doc (not arglist-only?))))
169      ((and (macro? obj)
170            (macro-transformer obj)
171            (closure? (macro-transformer obj))
172            (procedure-documentation (macro-transformer obj)))
173       ;; Documentation may be in the doc string of the transformer, as
174       ;; is in session.scm (help).
175       => (lambda (doc)
176            (doc->arglist doc (not arglist-only?))))
177      ((procedure? obj)
178       ;; Return a message about the arity of the procedure.
179       (with-output-to-string
180         (lambda () (arity obj))))
181      (else #f)))
182
183 (define-public (ilisp-print-info-message sym package)
184   "Evaluate SYM in PACKAGE and print an informational message about
185 the value.  For procedures, the arglist is printed.
186 This procedure is invoked by the electric space key."
187   (let ((obj (catch #t
188                     (lambda ()
189                       (eval-in-package sym
190                                        (string->module package)))
191                     (lambda args #f))))
192                      
193     (cond
194      ((and obj
195            (info-message sym obj #f #f))
196       => (lambda (message)
197            (display message)
198            (newline))))))
199
200 (define (if-defined symbol package
201                            defined-procedure undefined-procedure)
202   (let ((obj (catch #t
203                     (lambda ()
204                       (list (eval-in-package symbol
205                                              (string->module package))))
206                     (lambda args #f))))
207     (if obj
208         (defined-procedure (car obj))
209         (undefined-procedure))))
210
211 (define (strip-parens s)
212   (if (and (string=? (substring s 0 1) "(")
213            (string=? (substring s (- (string-length s) 1)) ")"))
214       (substring s 1 (- (string-length s) 1))
215       s))      
216
217 (define (symbol-not-present symbol package)
218   (display "Symbol `")
219   (display symbol)
220   (display "' not present in ")
221   (cond
222    ((string=? "nil" package)
223     (display "the current module `")
224     (for-each display (module-name (current-module)))
225     (display "'"))
226    (else
227     (display "module `")
228     (display (strip-parens package))
229     (display "'")))
230   (display ".\n"))
231
232 (define-public (ilisp-arglist symbol package)
233   "Evaluate SYMBOL in PACKAGE and print the arglist if we have a
234 procedure. This procedure is invoked by `arglist-lisp'."
235   (if-defined symbol package
236               (lambda (obj)
237                 (cond
238                  ((info-message symbol obj #t #t)
239                   => (lambda (message)
240                        (display message)
241                        (newline)))
242                  (else
243                   (display "Can't get arglist.")
244                   (newline))))
245               (lambda ()
246                 (symbol-not-present symbol package))))
247
248 (define-public (ilisp-help symbol package)
249   "Evaluate SYMBOL in PACKAGE and print help for it."
250   (if-defined symbol package
251               (lambda (obj)
252                 (let ((doc (object-documentation obj)))
253                   (if doc
254                       (display doc)
255                       (display "No documentation."))
256                   (newline)))
257               (lambda ()
258                 (symbol-not-present symbol package))))
259
260 (define (word-separator? ch)
261   (or (char=? ch #\-)
262       (char=? ch #\:)
263       (char=? ch #\_)
264       (char=? ch #\/)))
265
266 (define (string-pred-rindex str pred)
267   (let loop ((index (- (string-length str) 1)))
268     (cond
269      ((negative? index) #f)
270      ((pred (string-ref str index)) index)
271      (else (loop (- index 1))))))
272
273 (define (separate-fields-before-predicate pred str ret)
274   (let loop ((fields '())
275              (str str))
276     (cond
277      ((string-pred-rindex str pred)
278       => (lambda (w) (loop (cons (make-shared-substring str w) fields)
279                            (make-shared-substring str 0 w))))
280      (else (apply ret str fields)))))
281
282 (define (make-word-regexp str)
283   (apply string-append
284          (cons "^"
285                (map (lambda (word)
286                       (string-append (regexp-quote word) "[^-:/_]*"))
287                     (separate-fields-before-predicate word-separator?
288                                                       str list)))))           
289
290 (define-public (ilisp-matching-symbols string package function? external? prefix?)
291   (write (map (lambda (sym) (list (symbol->string sym)))
292        (let ((regexp (if (eq? prefix? 't)
293                          (string-append "^" (regexp-quote string))
294                          (make-word-regexp string)))
295              (a-i apropos-internal))
296          (save-module-excursion
297           (lambda ()
298             (set-current-module (string->module package))
299             (a-i regexp))))))
300   (newline))
301
302 (define (last l)
303   (cond ((and (pair? l) (not (null? (cdr l))))
304          (last (cdr l)))
305         (else (car l))))
306
307 (define eval-in-package
308   ;; A two-argument version of `eval'
309   (if (= (car (procedure-property eval 'arity)) 2)
310       (lambda (expression environment)  ; we have a R5RS eval
311         (save-module-excursion
312          (lambda ()
313            (eval expression environment))))
314       (lambda (expression environment)  ; we have a one-arg eval (Guile <= 1.4)
315         (save-module-excursion
316          (lambda ()
317            (set-current-module environment)
318            (eval expression))))))  
319
320 (define-public (ilisp-get-package sequence-of-defines)
321   "Get the last module name defined in the sequence of define-module forms."
322   ;; First eval the sequence-of-defines.  This will register the
323   ;; module with the Guile interpreter if it isn't there already.
324   ;; Otherwise `resolve-module' will give us a bad environment later,
325   ;; which just makes trouble.
326   (let ((name
327          (eval-in-package 
328           (append sequence-of-defines
329                   '((module-name (current-module))))
330           (string->module "(guile-user)"))))
331     (cond
332      ((pair? name)
333       ;; This version of Guile has a module-name procedure that
334       ;; returns the full module name.  Good.
335       (write name))
336      (else 
337       ;; Now we have the name of the module -- but only the last
338       ;; component.  We need to "parse" the sequence-of-defines
339       ;; ourselves.
340       (let ((last-form (last sequence-of-defines)))
341         (cond ((and (pair? last-form)
342                     (eq? (car last-form) 'define-module))
343                (write (cadr last-form)))
344               (else (write '(guile-user))))))))
345   (newline))
346
347 (define-public (ilisp-in-package package)
348   (set-current-module (string->module package))
349   (process-use-modules '(((guile-ilisp))))
350   *unspecified*)
351
352 (define-public (ilisp-eval form package filename line)
353   "Evaluate FORM in PACKAGE recording FILENAME as the source file
354 and LINE as the source code line there."
355   (eval-in-package
356    (read-from-string/source form filename line 1)
357    (string->module package)))
358
359 (define-public (ilisp-trace symbol package breakp)
360   (trace (eval-in-package symbol (string->module package)))
361   *unspecified*)
362
363 (define-public (ilisp-untrace symbol package)
364   (untrace (eval-in-package symbol (string->module package)))
365   *unspecified*)
366
367 (define (or-map* f list)
368   "Apply f to successive elements of l until exhaustion or improper end
369 or while f returns #f. If returning early, return the return value of f."
370   (let loop ((result #f)
371              (l list))
372     (or result
373         (and (pair? l)
374              (loop (f (car l)) (cdr l))))))
375
376 (define-public (ilisp-source-file symbol package)
377   "Find the source file of SYMBOL's definition in PACKAGE."
378   (catch #t
379          (lambda ()
380            (let ((value (eval-in-package (read-from-string symbol)
381                                          (string->module package))))
382              (cond
383               ((and (procedure? value)
384                     (procedure-source value))
385                => (lambda (source)
386                     (and=>
387                      (or-map* (lambda (s)
388                                 (false-if-exception
389                                  (source-property s 'filename)))
390                               source)
391                      (lambda (filename) (throw 'result filename))))))
392              (write 'nil)))
393          (lambda (key . args)
394            (if (eq? key 'result)
395                (begin (write (car args)) (newline) (write #t))
396                (begin (write 'nil)))))
397   (newline))
398
399 (define-public (ilisp-macroexpand-1 expression package)
400   (write (save-module-excursion
401    (lambda ()
402      (set-current-module (string->module package))
403      (macroexpand-1 (read-from-string expression)))))
404   (newline))
405
406 (define-public (ilisp-macroexpand expression package)
407   (write (save-module-excursion
408    (lambda ()
409      (set-current-module (string->module package))
410      (macroexpand (read-from-string expression)))))
411   (newline))
412