pp.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. ; Copyright (c) 1993-2008 by Richard Kelsey and Jonathan Rees. See file COPYING.
  2. ;;;; A pretty-printer
  3. ; This isn't exactly in the spirit of the rest of the Scheme 48
  4. ; system. It's too hairy, and it has unexploited internal generality.
  5. ; It really ought to be rewritten. In addition, it seems to be buggy
  6. ; -- it sometimes prints unnecessarily wide lines. Usually it's
  7. ; better than no pretty printer at all, so we tolerate it.
  8. ; From: ramsdell@linus.mitre.org
  9. ; Date: Wed, 12 Sep 1990 05:14:49 PDT
  10. ;
  11. ; As you noted in your comments, pp.scm is not a straight forward
  12. ; program. You could add some comments that would greatly ease the task
  13. ; of figuring out what his going on. In particular, you should describe
  14. ; the interface of various objects---most importantly the interface of a
  15. ; formatter. You might also add some description as to what protocol
  16. ; they are to follow.
  17. ; Other things to implement some day:
  18. ; - LET, LET*, LETREC binding lists should be printed vertically if longer
  19. ; than about 30 characters
  20. ; - COND clauses should all be printed vertically if the COND is vertical
  21. ; - Add an option to lowercase or uppercase symbols and named characters.
  22. ; - Parameters controlling behavior of printer should be passed around
  23. ; - Do something about choosing between #f and ()
  24. ; - Insert line breaks intelligently following head of symbol-headed list,
  25. ; when necessary
  26. ; - Some equivalents of *print-level*, *print-length*, *print-circle*.
  27. ; Possible strategies:
  28. ; (foo x y z) Horizontal = infinity sticky
  29. ; (foo x y One sticky + one + body (e.g. named LET)
  30. ; z
  31. ; w)
  32. ; (foo x One + body
  33. ; y
  34. ; z)
  35. ; (foo x Two + body
  36. ; y
  37. ; z)
  38. ; (foo x Big ell = infinity + body (combination)
  39. ; y
  40. ; z)
  41. ; (foo Little ell, zero + body (combination)
  42. ; x
  43. ; y)
  44. ; (foo Vertical
  45. ; x
  46. ; y)
  47. ;
  48. ; Available height/width tradeoffs:
  49. ; Combination:
  50. ; Horizontal, big ell, or little ell.
  51. ; Special form:
  52. ; Horizontal, or M sticky + N + body.
  53. ; Random (e.g. vector, improper list, non-symbol-headed list):
  54. ; Horizontal, or vertical. (Never zero plus body.)
  55. (define (p x . port-option)
  56. (let ((port (if (pair? port-option) (car port-option)
  57. (current-output-port))))
  58. (pretty-print x port 0)
  59. (newline port)))
  60. (define *line-width* 80)
  61. (define *single-line-special-form-limit* 30)
  62. ; Stream primitives
  63. (define head car)
  64. (define (tail s) (force (cdr s)))
  65. (define (map-stream proc stream)
  66. (cons (proc (head stream))
  67. (delay (map-stream proc (tail stream)))))
  68. (define (stream-ref stream n)
  69. (if (= n 0)
  70. (head stream)
  71. (stream-ref (tail stream) (- n 1))))
  72. ; Printer
  73. (define (pretty-print obj port pos)
  74. (let ((node (pp-prescan obj 0)))
  75. ; (if (> (column-of (node-dimensions node)) *line-width*)
  76. ; ;; Eventually add a pass to change format of selected combinations
  77. ; ;; from big-ell to little-ell.
  78. ; (begin (display ";** too wide - ")
  79. ; (write (node-dimensions node))
  80. ; (newline)))
  81. (print-node node port pos)))
  82. (define make-node list)
  83. (define (node-dimensions node)
  84. ((car node)))
  85. (define (node-pass-2 node pos)
  86. ((cadr node) pos))
  87. (define (print-node node port pos)
  88. ((caddr node) port pos))
  89. (define (pp-prescan obj hang)
  90. (cond ((symbol? obj)
  91. (make-leaf (string-length (symbol->string obj))
  92. obj hang))
  93. ((number? obj)
  94. (make-leaf (string-length (number->string obj))
  95. obj hang))
  96. ((boolean? obj)
  97. (make-leaf 2 obj hang))
  98. ((string? obj)
  99. ;;++ Should count number of backslashes and quotes
  100. (make-leaf (+ (string-length obj) 2) obj hang))
  101. ((char? obj)
  102. (make-leaf (case obj
  103. ((#\space) 7)
  104. ((#\newline) 9)
  105. (else 3))
  106. obj hang))
  107. ((pair? obj)
  108. (pp-prescan-pair obj hang))
  109. ((vector? obj)
  110. (pp-prescan-vector obj hang))
  111. (else
  112. (pp-prescan-random obj hang))))
  113. (define (make-leaf width obj hang)
  114. (let ((width (+ width hang)))
  115. (make-node (lambda () width)
  116. (lambda (pos)
  117. (+ pos width))
  118. (lambda (port pos)
  119. (write obj port)
  120. (do ((i 0 (+ i 1)))
  121. ((>= i hang) (+ pos width))
  122. (write-char #\) port))))))
  123. (define (make-prefix-node string node)
  124. (let ((len (string-length string)))
  125. (make-node (lambda ()
  126. (+ (node-dimensions node) len))
  127. (lambda (pos)
  128. (node-pass-2 node (+ pos len)))
  129. (lambda (port pos)
  130. (display string port)
  131. (print-node node port (+ pos len))))))
  132. (define (pp-prescan-vector obj hang)
  133. (if (= (vector-length obj) 0)
  134. (make-leaf 3 obj hang)
  135. (make-prefix-node "#" (pp-prescan-list (vector->list obj) #t hang))))
  136. ; Improve later.
  137. (define (pp-prescan-random obj hang)
  138. (let ((l (disclose obj)))
  139. (if (list? l)
  140. (make-prefix-node "#." (pp-prescan-list l #t hang))
  141. (make-leaf 25 obj hang)))) ;Very random number
  142. (define (pp-prescan-pair obj hang)
  143. (cond ((read-macro-inverse obj)
  144. =>
  145. (lambda (inverse)
  146. (make-prefix-node inverse (pp-prescan (cadr obj) hang))))
  147. (else
  148. (pp-prescan-list obj #f hang))))
  149. (define (pp-prescan-list obj random? hang)
  150. (let loop ((l obj) (z '()))
  151. (if (pair? (cdr l))
  152. (loop (cdr l)
  153. (cons (pp-prescan (car l) 0) z))
  154. (make-list-node
  155. (reverse
  156. (if (null? (cdr l))
  157. (cons (pp-prescan (car l) (+ hang 1)) z)
  158. (cons (make-prefix-node ". " (pp-prescan (cdr l) (+ hang 1)))
  159. (cons (pp-prescan (car l) 0) z))))
  160. obj
  161. (or random? (not (null? (cdr l))))))))
  162. ; Is it sufficient to tell parent node:
  163. ; At a cost of X line breaks, I can make myself narrower by Y columns. ?
  164. ; Then how do we decide whether we narrow ourselves or some of our children?
  165. (define (make-list-node node-list obj random?)
  166. (let* ((random? (or random?
  167. ;; Heuristic for things like do, cond, let, ...
  168. (not (symbol? (car obj)))
  169. (eq? (car obj) 'else)))
  170. (probe (if (not random?)
  171. (indentation-for (car obj))
  172. #f))
  173. (format horizontal-format)
  174. (dimensions (compute-dimensions node-list format))
  175. (go-non-horizontal
  176. (lambda (col)
  177. (set! format
  178. (cond (random? vertical-format)
  179. (probe (probe obj))
  180. (else big-ell-format)))
  181. (let* ((start-col (+ col 1))
  182. (col (node-pass-2 (car node-list) start-col))
  183. (final-col
  184. (format (cdr node-list)
  185. (lambda (node col target-col)
  186. (node-pass-2 node target-col))
  187. start-col
  188. (+ col 1)
  189. col)))
  190. (set! dimensions (compute-dimensions node-list format))
  191. final-col))))
  192. (if (> dimensions
  193. (if probe
  194. *single-line-special-form-limit*
  195. *line-width*))
  196. (go-non-horizontal 0))
  197. (make-node (lambda () dimensions)
  198. (lambda (col) ;Pass 2: if necessary, go non-horizontal
  199. (let ((defacto (+ col (column-of dimensions))))
  200. (if (> defacto *line-width*)
  201. (go-non-horizontal col)
  202. defacto)))
  203. (lambda (port pos)
  204. (write-char #\( port)
  205. (let* ((pos (+ pos 1))
  206. (start-col (column-of pos))
  207. (pos (print-node (car node-list) port pos)))
  208. (format (cdr node-list)
  209. (lambda (node pos target-col)
  210. (let ((pos (go-to-column target-col
  211. port pos)))
  212. (print-node node port pos)))
  213. start-col
  214. (+ (column-of pos) 1)
  215. pos))))))
  216. (define (compute-dimensions node-list format)
  217. (let* ((start-col 1) ;open paren
  218. (pos (+ (make-position start-col 0)
  219. (node-dimensions (car node-list)))))
  220. (format (cdr node-list)
  221. (lambda (node pos target-col)
  222. (let* ((dims (node-dimensions node))
  223. (lines (+ (line-of pos) (line-of dims)))
  224. (width (+ target-col (column-of dims))))
  225. (if (>= (column-of pos) target-col)
  226. ;; Line break required
  227. (make-position
  228. (max (column-of pos) width)
  229. (+ lines 1))
  230. (make-position width lines))))
  231. start-col
  232. (+ (column-of pos) 1) ;first-col
  233. pos)))
  234. ; Three positions are significant
  235. ; (foo baz ...)
  236. ; ^ ^ ^
  237. ; | | +--- (column-of pos)
  238. ; | +------ first-col
  239. ; +---------- start-col
  240. ; Separators
  241. (define on-same-line
  242. (lambda (start-col first-col pos)
  243. start-col first-col ;ignored
  244. (+ (column-of pos) 1)))
  245. (define indent-under-first
  246. (lambda (start-col first-col pos)
  247. start-col ;ignored
  248. first-col))
  249. (define indent-for-body
  250. (lambda (start-col first-col pos)
  251. first-col ;ignored
  252. (+ start-col 1)))
  253. (define indent-under-head
  254. (lambda (start-col first-col pos)
  255. first-col ;ignored
  256. start-col))
  257. ; Format constructors
  258. (define (once separator format)
  259. (lambda (tail proc start-col first-col pos)
  260. (if (null? tail)
  261. pos
  262. (let ((target-col (separator start-col first-col pos)))
  263. (format (cdr tail)
  264. proc
  265. start-col
  266. first-col
  267. (proc (car tail) pos target-col))))))
  268. (define (indefinitely separator)
  269. (letrec ((self (once separator ;eta
  270. (lambda (tail proc start-col first-col pos)
  271. (self tail proc start-col first-col pos)))))
  272. self))
  273. (define (repeatedly separator count format)
  274. (do ((i 0 (+ i 1))
  275. (format format
  276. (once separator format)))
  277. ((>= i count) format)))
  278. ; Particular formats
  279. (define vertical-format
  280. (indefinitely indent-under-head))
  281. (define horizontal-format
  282. (indefinitely on-same-line))
  283. (define big-ell-format
  284. (indefinitely indent-under-first))
  285. (define little-ell-format
  286. (indefinitely indent-for-body))
  287. (define format-for-named-let
  288. (repeatedly on-same-line 2 (indefinitely indent-for-body)))
  289. (define hook-formats
  290. (letrec ((stream (cons little-ell-format
  291. (delay (map-stream (lambda (format)
  292. (once indent-under-first format))
  293. stream)))))
  294. stream))
  295. ; Hooks for special forms.
  296. ; A hook maps an expression to a format.
  297. (define (compute-let-indentation exp)
  298. (if (and (not (null? (cdr exp)))
  299. (symbol? (cadr exp)))
  300. format-for-named-let
  301. (stream-ref hook-formats 1)))
  302. (define hook
  303. (let ((hooks (map-stream (lambda (format)
  304. (lambda (exp) exp ;ignored
  305. format))
  306. hook-formats)))
  307. (lambda (n)
  308. (stream-ref hooks n))))
  309. ; Table of indent hooks.
  310. (define indentations (make-table))
  311. (define (indentation-for name)
  312. (table-ref indentations name))
  313. (define (define-indentation name n)
  314. (table-set! indentations
  315. name
  316. (if (integer? n) (hook n) n)))
  317. ; Indent hooks for Revised^n Scheme.
  318. (for-each (lambda (name)
  319. (define-indentation name 1))
  320. '(lambda define define-syntax let* letrec let-syntax letrec-syntax
  321. case call-with-values call-with-input-file
  322. call-with-output-file with-input-from-file
  323. with-output-to-file syntax-rules))
  324. (define-indentation 'do 2)
  325. (define-indentation 'call-with-current-continuation 0)
  326. (define-indentation 'let compute-let-indentation)
  327. ; Kludge to force vertical printing (do AND and OR as well?)
  328. (define-indentation 'if (lambda (exp) big-ell-format))
  329. (define-indentation 'cond (lambda (exp) big-ell-format))
  330. ; Other auxiliaries
  331. (define (go-to-column target-col port pos) ;=> pos
  332. ;; Writes at least one space or newline
  333. (let* ((column (column-of pos))
  334. (line (if (>= column target-col)
  335. (+ (line-of pos) 1)
  336. (line-of pos))))
  337. (do ((column (if (>= column target-col)
  338. (begin (newline port) 0)
  339. column)
  340. (+ column 1)))
  341. ((>= column target-col)
  342. (make-position column line))
  343. (write-char #\space port))))
  344. (define (make-position column line)
  345. (+ column (* line 1000)))
  346. (define (column-of pos)
  347. (remainder pos 1000))
  348. (define (line-of pos)
  349. (quotient pos 1000))
  350. (define (read-macro-inverse x)
  351. (cond ((and (pair? x)
  352. (pair? (cdr x))
  353. (null? (cddr x)))
  354. (case (car x)
  355. ((quote) "'")
  356. ((quasiquote) "`")
  357. ((unquote) ",")
  358. ((unquote-splicing) ",@")
  359. (else #f)))
  360. (else #f)))
  361. ; For the command processor:
  362. ;(define-command 'p "<exp>" "pretty-print" '(expression)
  363. ; (p (eval expression (user-package)) (command-output)))