reflection.scm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. ;;;; (texinfo reflection) -- documenting Scheme as stexinfo
  2. ;;;;
  3. ;;;; Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
  4. ;;;; Copyright (C) 2003,2004,2009 Andy Wingo <wingo at pobox dot com>
  5. ;;;;
  6. ;;;; This library is free software; you can redistribute it and/or
  7. ;;;; modify it under the terms of the GNU Lesser General Public
  8. ;;;; License as published by the Free Software Foundation; either
  9. ;;;; version 3 of the License, or (at your option) any later version.
  10. ;;;;
  11. ;;;; This library 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 GNU
  14. ;;;; Lesser General Public License for more details.
  15. ;;;;
  16. ;;;; You should have received a copy of the GNU Lesser General Public
  17. ;;;; License along with this library; if not, write to the Free Software
  18. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  19. ;;;;
  20. ;;; Commentary:
  21. ;;
  22. ;;Routines to generare @code{stexi} documentation for objects and
  23. ;;modules.
  24. ;;
  25. ;;Note that in this context, an @dfn{object} is just a value associated
  26. ;;with a location. It has nothing to do with GOOPS.
  27. ;;
  28. ;;; Code:
  29. (define-module (texinfo reflection)
  30. #:use-module ((srfi srfi-1) #:select (append-map))
  31. #:use-module (oop goops)
  32. #:use-module (texinfo)
  33. #:use-module (texinfo plain-text)
  34. #:use-module (srfi srfi-13)
  35. #:use-module (ice-9 session)
  36. #:use-module (ice-9 documentation)
  37. #:use-module (ice-9 optargs)
  38. #:use-module ((sxml transform) #:select (pre-post-order))
  39. #:export (module-stexi-documentation
  40. script-stexi-documentation
  41. object-stexi-documentation
  42. package-stexi-standard-copying
  43. package-stexi-standard-titlepage
  44. package-stexi-generic-menu
  45. package-stexi-standard-menu
  46. package-stexi-extended-menu
  47. package-stexi-standard-prologue
  48. package-stexi-documentation
  49. package-stexi-documentation-for-include))
  50. ;; List for sorting the definitions in a module
  51. (define defs
  52. '(deftp defcv defivar deftypeivar defop deftypeop defmethod
  53. deftypemethod defopt defvr defvar deftypevr deftypevar deffn
  54. deftypefn defmac defspec defun deftypefun))
  55. (define (sort-defs ordering a b)
  56. (define (def x)
  57. ;; a and b are lists of the form ((anchor ...) (def* ...)...)
  58. (cadr x))
  59. (define (name x)
  60. (cadr (assq 'name (cdadr (def x)))))
  61. (define (priority x)
  62. (list-index defs (car (def x))))
  63. (define (order x)
  64. (or (list-index ordering (string->symbol (name x)))
  65. ;; if the def is not in the list, a big number
  66. 1234567890))
  67. (define (compare-in-order proc eq? < . args)
  68. (if (not (eq? (proc a) (proc b)))
  69. (< (proc a) (proc b))
  70. (or (null? args)
  71. (apply compare-in-order args))))
  72. (compare-in-order order = <
  73. priority = <
  74. name string=? string<=?))
  75. (define (list*-join l infix restfix)
  76. (let lp ((in l) (out '()))
  77. (cond ((null? in) (reverse! out))
  78. ((symbol? in) (reverse! (cons* in restfix out)))
  79. (else (lp (cdr in) (if (null? out)
  80. (list (car in))
  81. (cons* (car in) infix out)))))))
  82. (define (process-args args)
  83. (map (lambda (x) (if (string? x) x (object->string x)))
  84. (list*-join (or args '())
  85. " " " . ")))
  86. (define (get-proc-args proc)
  87. (cond
  88. ((procedure-arguments proc)
  89. => (lambda (args)
  90. (let ((required-args (assq-ref args 'required))
  91. (optional-args (assq-ref args 'optional))
  92. (keyword-args (assq-ref args 'keyword))
  93. (rest-arg (assq-ref args 'rest)))
  94. (process-args
  95. (append
  96. ;; start with the required args...
  97. (map symbol->string required-args)
  98. ;; add any optional args if needed...
  99. (map (lambda (a)
  100. (if (list? a)
  101. (format #f "[~a = ~s]" (car a) (cadr a))
  102. (format #f "[~a]" a)))
  103. optional-args)
  104. ;; now the keyword args..
  105. (map (lambda (a)
  106. (if (pair? a)
  107. (format #f "[~a]" (car a))
  108. (format #f "[#:~a]" a)))
  109. keyword-args)
  110. ;; now the rest arg...
  111. (if rest-arg
  112. (list "." (symbol->string rest-arg))
  113. '()))))))))
  114. (define (macro-arguments name type transformer)
  115. (process-args
  116. (case type
  117. ((syntax-rules)
  118. (let ((patterns (procedure-property transformer 'patterns)))
  119. (if (pair? patterns)
  120. (car patterns)
  121. '())))
  122. ((identifier-syntax)
  123. '())
  124. ((defmacro)
  125. (or (procedure-property transformer 'defmacro-args)
  126. '()))
  127. (else
  128. ;; a procedural (syntax-case) macro. how to document these?
  129. '()))))
  130. (define (macro-additional-stexi name type transformer)
  131. (case type
  132. ((syntax-rules)
  133. (let ((patterns (procedure-property transformer 'patterns)))
  134. (if (pair? patterns)
  135. (map (lambda (x)
  136. `(defspecx (% (name ,name)
  137. (arguments ,@(process-args x)))))
  138. (cdr patterns))
  139. '())))
  140. (else
  141. '())))
  142. (define many-space? (make-regexp "[[:space:]][[:space:]][[:space:]]"))
  143. (define initial-space? (make-regexp "^[[:space:]]"))
  144. (define (string->stexi str)
  145. (or (and (or (not str) (string-null? str))
  146. '(*fragment*))
  147. (and (or (string-index str #\@)
  148. (and (not (regexp-exec many-space? str))
  149. (not (regexp-exec initial-space? str))))
  150. (false-if-exception
  151. (texi-fragment->stexi str)))
  152. `(*fragment* (verbatim ,str))))
  153. (define method-formals
  154. (and (defined? 'method-formals) method-formals))
  155. (define (method-stexi-arguments method)
  156. (cond
  157. (method-formals
  158. (let lp ((formals (method-formals method))
  159. (specializers (method-specializers method))
  160. (out '()))
  161. (define (arg-texinfo formal specializer)
  162. `(" (" (var ,(symbol->string formal)) " "
  163. (code ,(symbol->string (class-name specializer))) ")"))
  164. (cond
  165. ((null? formals) (reverse out))
  166. ((pair? formals)
  167. (lp (cdr formals) (cdr specializers)
  168. (append (reverse (arg-texinfo (car formals) (car specializers)))
  169. out)))
  170. (else
  171. (append (reverse out) (arg-texinfo formals specializers)
  172. (list "..."))))))
  173. ((method-source method)
  174. (let lp ((bindings (cadr (method-source method))) (out '()))
  175. (define (arg-texinfo arg)
  176. `(" (" (var ,(symbol->string (car arg))) " "
  177. (code ,(symbol->string (cadr arg))) ")"))
  178. (cond
  179. ((null? bindings)
  180. (reverse out))
  181. ((not (pair? (car bindings)))
  182. (append (reverse out) (arg-texinfo bindings) (list "...")))
  183. (else
  184. (lp (cdr bindings)
  185. (append (reverse (arg-texinfo (car bindings))) out))))))
  186. (else (warn method) '())))
  187. (define* (object-stexi-documentation object #:optional (name "[unknown]")
  188. #:key (force #f))
  189. (if (symbol? name)
  190. (set! name (symbol->string name)))
  191. (let ((stexi ((lambda (x)
  192. (cond ((string? x) (string->stexi x))
  193. ((and (pair? x) (eq? (car x) '*fragment*)) x)
  194. (force `(*fragment*))
  195. (else #f)))
  196. (object-documentation
  197. (if (is-a? object <method>)
  198. (method-procedure object)
  199. object)))))
  200. (define (make-def type args)
  201. `(,type (% ,@args) ,@(cdr stexi)))
  202. (cond
  203. ((not stexi) #f)
  204. ;; stexi is now a list, headed by *fragment*.
  205. ((and (pair? (cdr stexi)) (pair? (cadr stexi))
  206. (memq (caadr stexi) defs))
  207. ;; it's already a deffoo.
  208. stexi)
  209. ((is-a? object <class>)
  210. (make-def 'deftp `((name ,name)
  211. (category "Class"))))
  212. ((is-a? object <macro>)
  213. (let* ((proc (macro-transformer object))
  214. (type (and proc (procedure-property proc 'macro-type))))
  215. `(defspec (% (name ,name)
  216. (arguments ,@(macro-arguments name type proc)))
  217. ,@(macro-additional-stexi name type proc)
  218. ,@(cdr stexi))))
  219. ((is-a? object <procedure>)
  220. (make-def 'defun `((name ,name)
  221. (arguments ,@(get-proc-args object)))))
  222. ((is-a? object <method>)
  223. (make-def 'deffn `((category "Method")
  224. (name ,name)
  225. (arguments ,@(method-stexi-arguments object)))))
  226. ((is-a? object <generic>)
  227. `(*fragment*
  228. ,(make-def 'deffn `((name ,name)
  229. (category "Generic")))
  230. ,@(map
  231. (lambda (method)
  232. (object-stexi-documentation method name #:force force))
  233. (generic-function-methods object))))
  234. (else
  235. (make-def 'defvar `((name ,name)))))))
  236. (define (module-name->node-name sym-name)
  237. (string-join (map symbol->string sym-name) " "))
  238. ;; this copied from (ice-9 session); need to find a better way
  239. (define (module-filename name)
  240. (let* ((name (map symbol->string name))
  241. (reverse-name (reverse name))
  242. (leaf (car reverse-name))
  243. (dir-hint-module-name (reverse (cdr reverse-name)))
  244. (dir-hint (apply string-append
  245. (map (lambda (elt)
  246. (string-append elt "/"))
  247. dir-hint-module-name))))
  248. (%search-load-path (in-vicinity dir-hint leaf))))
  249. (define (read-module name)
  250. (let ((filename (module-filename name)))
  251. (if filename
  252. (let ((port (open-input-file filename)))
  253. (let lp ((out '()) (form (read port)))
  254. (if (eof-object? form)
  255. (reverse out)
  256. (lp (cons form out) (read port)))))
  257. '())))
  258. (define (module-export-list sym-name)
  259. (define (module-form-export-list form)
  260. (and (pair? form)
  261. (eq? (car form) 'define-module)
  262. (equal? (cadr form) sym-name)
  263. (and=> (memq #:export (cddr form)) cadr)))
  264. (let lp ((forms (read-module sym-name)))
  265. (cond ((null? forms) '())
  266. ((module-form-export-list (car forms)) => identity)
  267. (else (lp (cdr forms))))))
  268. (define* (module-stexi-documentation sym-name
  269. #:key (docs-resolver
  270. (lambda (name def) def)))
  271. "Return documentation for the module named @var{sym-name}. The
  272. documentation will be formatted as @code{stexi}
  273. (@pxref{texinfo,texinfo})."
  274. (let* ((commentary (and=> (module-commentary sym-name)
  275. (lambda (x) (string-trim-both x #\newline))))
  276. (stexi (string->stexi commentary))
  277. (node-name (module-name->node-name sym-name))
  278. (name-str (with-output-to-string
  279. (lambda () (display sym-name))))
  280. (module (resolve-interface sym-name))
  281. (export-list (module-export-list sym-name)))
  282. (define (anchor-name sym)
  283. (string-append node-name " " (symbol->string sym)))
  284. (define (make-defs)
  285. (sort!
  286. (module-map
  287. (lambda (sym var)
  288. `((anchor (% (name ,(anchor-name sym))))
  289. ,@((lambda (x)
  290. (if (eq? (car x) '*fragment*)
  291. (cdr x)
  292. (list x)))
  293. (if (variable-bound? var)
  294. (docs-resolver
  295. sym
  296. (object-stexi-documentation (variable-ref var) sym
  297. #:force #t))
  298. (begin
  299. (warn "variable unbound!" sym)
  300. `(defvar (% (name ,(symbol->string sym)))
  301. "[unbound!]"))))))
  302. module)
  303. (lambda (a b) (sort-defs export-list a b))))
  304. `(texinfo (% (title ,name-str))
  305. (node (% (name ,node-name)))
  306. (section "Overview")
  307. ,@(cdr stexi)
  308. (section "Usage")
  309. ,@(apply append! (make-defs)))))
  310. (define (script-stexi-documentation scriptpath)
  311. "Return documentation for given script. The documentation will be
  312. taken from the script's commentary, and will be returned in the
  313. @code{stexi} format (@pxref{texinfo,texinfo})."
  314. (let ((commentary (file-commentary scriptpath)))
  315. `(texinfo (% (title ,(basename scriptpath)))
  316. (node (% (name ,(basename scriptpath))))
  317. ,@(if commentary
  318. (cdr
  319. (string->stexi
  320. (string-trim-both commentary #\newline)))
  321. '()))))
  322. (cond
  323. ((defined? 'add-value-help-handler!)
  324. (add-value-help-handler!
  325. (lambda (name value)
  326. (stexi->plain-text
  327. (object-stexi-documentation value name #:force #t))))
  328. (add-name-help-handler!
  329. (lambda (name)
  330. (and (list? name)
  331. (and-map symbol? name)
  332. (stexi->plain-text (module-stexi-documentation name)))))))
  333. ;; we could be dealing with an old (ice-9 session); fondle it to get
  334. ;; module-commentary
  335. (define module-commentary (@@ (ice-9 session) module-commentary))
  336. (define (package-stexi-standard-copying name version updated years
  337. copyright-holder permissions)
  338. "Create a standard texinfo @code{copying} section.
  339. @var{years} is a list of years (as integers) in which the modules
  340. being documented were released. All other arguments are strings."
  341. `(copying
  342. (para "This manual is for " ,name
  343. " (version " ,version ", updated " ,updated ")")
  344. (para "Copyright " ,(string-join (map number->string years) ",")
  345. " " ,copyright-holder)
  346. (quotation
  347. (para ,permissions))))
  348. (define (package-stexi-standard-titlepage name version updated authors)
  349. "Create a standard GNU title page.
  350. @var{authors} is a list of @code{(@var{name} . @var{email})}
  351. pairs. All other arguments are strings.
  352. Here is an example of the usage of this procedure:
  353. @smallexample
  354. (package-stexi-standard-titlepage
  355. \"Foolib\"
  356. \"3.2\"
  357. \"26 September 2006\"
  358. '((\"Alyssa P Hacker\" . \"alyssa@@example.com\"))
  359. '(2004 2005 2006)
  360. \"Free Software Foundation, Inc.\"
  361. \"Standard GPL permissions blurb goes here\")
  362. @end smallexample
  363. "
  364. `(;(setchapternewpage (% (all "odd"))) makes manuals too long
  365. (titlepage
  366. (title ,name)
  367. (subtitle "version " ,version ", updated " ,updated)
  368. ,@(map (lambda (pair)
  369. `(author ,(car pair)
  370. " (" (email ,(cdr pair)) ")"))
  371. authors)
  372. (page)
  373. (vskip (% (all "0pt plus 1filll")))
  374. (insertcopying))))
  375. (define (package-stexi-generic-menu name entries)
  376. "Create a menu from a generic alist of entries, the car of which
  377. should be the node name, and the cdr the description. As an exception,
  378. an entry of @code{#f} will produce a separator."
  379. (define (make-entry node description)
  380. `("* " ,node "::"
  381. ,(make-string (max (- 21 (string-length node)) 2) #\space)
  382. ,@description "\n"))
  383. `((ifnottex
  384. (node (% (name "Top")))
  385. (top (% (title ,name)))
  386. (insertcopying)
  387. (menu
  388. ,@(apply
  389. append
  390. (map
  391. (lambda (entry)
  392. (if entry
  393. (make-entry (car entry) (cdr entry))
  394. '("\n")))
  395. entries))))
  396. (iftex
  397. (shortcontents))))
  398. (define (package-stexi-standard-menu name modules module-descriptions
  399. extra-entries)
  400. "Create a standard top node and menu, suitable for processing
  401. by makeinfo."
  402. (package-stexi-generic-menu
  403. name
  404. (let ((module-entries (map cons
  405. (map module-name->node-name modules)
  406. module-descriptions))
  407. (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
  408. `(,@module-entries
  409. ,@(separate-sections extra-entries)))))
  410. (define (package-stexi-extended-menu name module-pairs script-pairs
  411. extra-entries)
  412. "Create an \"extended\" menu, like the standard menu but with a
  413. section for scripts."
  414. (package-stexi-generic-menu
  415. name
  416. (let ((module-entries (map cons
  417. (map module-name->node-name
  418. (map car module-pairs))
  419. (map cdr module-pairs)))
  420. (script-entries (map cons
  421. (map basename (map car script-pairs))
  422. (map cdr script-pairs)))
  423. (separate-sections (lambda (x) (if (null? x) x (cons #f x)))))
  424. `(,@module-entries
  425. ,@(separate-sections script-entries)
  426. ,@(separate-sections extra-entries)))))
  427. (define (package-stexi-standard-prologue name filename category
  428. description copying titlepage
  429. menu)
  430. "Create a standard prologue, suitable for later serialization
  431. to texinfo and .info creation with makeinfo.
  432. Returns a list of stexinfo forms suitable for passing to
  433. @code{package-stexi-documentation} as the prologue. @xref{texinfo
  434. reflection package-stexi-documentation}, @ref{texinfo reflection
  435. package-stexi-standard-titlepage,package-stexi-standard-titlepage},
  436. @ref{texinfo reflection
  437. package-stexi-standard-copying,package-stexi-standard-copying},
  438. and @ref{texinfo reflection
  439. package-stexi-standard-menu,package-stexi-standard-menu}."
  440. `(,copying
  441. (dircategory (% (category ,category)))
  442. (direntry
  443. "* " ,name ": (" ,filename "). " ,description ".")
  444. ,@titlepage
  445. ,@menu))
  446. (define (stexi->chapter stexi)
  447. (pre-post-order
  448. stexi
  449. `((texinfo . ,(lambda (tag attrs node . body)
  450. `(,node
  451. (chapter ,@(assq-ref (cdr attrs) 'title))
  452. ,@body)))
  453. (*text* . ,(lambda (tag text) text))
  454. (*default* . ,(lambda args args)))))
  455. (define* (package-stexi-documentation modules name filename
  456. prologue epilogue
  457. #:key
  458. (module-stexi-documentation-args
  459. '())
  460. (scripts '()))
  461. "Create stexi documentation for a @dfn{package}, where a
  462. package is a set of modules that is released together.
  463. @var{modules} is expected to be a list of module names, where a
  464. module name is a list of symbols. The stexi that is returned will
  465. be titled @var{name} and a texinfo filename of @var{filename}.
  466. @var{prologue} and @var{epilogue} are lists of stexi forms that
  467. will be spliced into the output document before and after the
  468. generated modules documentation, respectively.
  469. @xref{texinfo reflection package-stexi-standard-prologue}, to
  470. create a conventional GNU texinfo prologue.
  471. @var{module-stexi-documentation-args} is an optional argument that, if
  472. given, will be added to the argument list when
  473. @code{module-texi-documentation} is called. For example, it might be
  474. useful to define a @code{#:docs-resolver} argument."
  475. (define (verify-modules-list l)
  476. (define (all pred l)
  477. (and (pred (car l))
  478. (or (null? (cdr l)) (all pred (cdr l)))))
  479. (false-if-exception
  480. (all (lambda (x) (all symbol? x)) modules)))
  481. (if (not (verify-modules-list modules))
  482. (error "expected modules to be a list of a list of symbols"
  483. modules))
  484. `(texinfo
  485. (% (title ,name)
  486. (filename ,filename))
  487. ,@prologue
  488. ,@(append-map (lambda (mod)
  489. (stexi->chapter
  490. (apply module-stexi-documentation
  491. mod module-stexi-documentation-args)))
  492. modules)
  493. ,@(append-map (lambda (script)
  494. (stexi->chapter
  495. (script-stexi-documentation script)))
  496. scripts)
  497. ,@epilogue))
  498. (define* (package-stexi-documentation-for-include modules module-descriptions
  499. #:key
  500. (module-stexi-documentation-args '()))
  501. "Create stexi documentation for a @dfn{package}, where a
  502. package is a set of modules that is released together.
  503. @var{modules} is expected to be a list of module names, where a
  504. module name is a list of symbols. Returns an stexinfo fragment.
  505. Unlike @code{package-stexi-documentation}, this function simply produces
  506. a menu and the module documentations instead of producing a full texinfo
  507. document. This can be useful if you write part of your manual by hand,
  508. and just use @code{@@include} to pull in the automatically generated
  509. parts.
  510. @var{module-stexi-documentation-args} is an optional argument that, if
  511. given, will be added to the argument list when
  512. @code{module-texi-documentation} is called. For example, it might be
  513. useful to define a @code{#:docs-resolver} argument."
  514. (define (make-entry node description)
  515. `("* " ,node "::"
  516. ,(make-string (max (- 21 (string-length node)) 2) #\space)
  517. ,@description "\n"))
  518. `(*fragment*
  519. (menu
  520. ,@(append-map (lambda (modname desc)
  521. (make-entry (module-name->node-name modname)
  522. desc))
  523. modules
  524. module-descriptions))
  525. ,@(append-map (lambda (modname)
  526. (stexi->chapter
  527. (apply module-stexi-documentation
  528. modname
  529. module-stexi-documentation-args)))
  530. modules)))
  531. ;;; arch-tag: bbe2bc03-e16d-4a9e-87b9-55225dc9836c