scsh-0.5/big/pp.scm

432 lines
12 KiB
Scheme

; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING.
;;;; A pretty-printer
; This isn't exactly in the spirit of the rest of the Scheme 48
; system. It's too hairy, and it has unexploited internal generality.
; It really ought to be rewritten. In addition, it seems to be buggy
; -- it sometimes prints unnecessarily wide lines. Usually it's
; better than no pretty printer at all, so we tolerate it.
; From: ramsdell@linus.mitre.org
; Date: Wed, 12 Sep 1990 05:14:49 PDT
;
; As you noted in your comments, pp.scm is not a straight forward
; program. You could add some comments that would greatly ease the task
; of figuring out what his going on. In particular, you should describe
; the interface of various objects---most importantly the interface of a
; formatter. You might also add some description as to what protocol
; they are to follow.
; Other things to implement some day:
; - LET, LET*, LETREC binding lists should be printed vertically if longer
; than about 30 characters
; - COND clauses should all be printed vertically if the COND is vertical
; - Add an option to lowercase or uppercase symbols and named characters.
; - Parameters controlling behavior of printer should be passed around
; - Do something about choosing between #f and ()
; - Insert line breaks intelligently following head of symbol-headed list,
; when necessary
; - Some equivalents of *print-level*, *print-length*, *print-circle*.
; Possible strategies:
; (foo x y z) Horizontal = infinity sticky
; (foo x y One sticky + one + body (e.g. named LET)
; z
; w)
; (foo x One + body
; y
; z)
; (foo x Two + body
; y
; z)
; (foo x Big ell = infinity + body (combination)
; y
; z)
; (foo Little ell, zero + body (combination)
; x
; y)
; (foo Vertical
; x
; y)
;
; Available height/width tradeoffs:
; Combination:
; Horizontal, big ell, or little ell.
; Special form:
; Horizontal, or M sticky + N + body.
; Random (e.g. vector, improper list, non-symbol-headed list):
; Horizontal, or vertical. (Never zero plus body.)
(define (p x . port-option)
(let ((port (if (pair? port-option) (car port-option)
(current-output-port))))
(pretty-print x port 0)
(newline port)))
(define *line-width* 80)
(define *single-line-special-form-limit* 30)
; Stream primitives
(define head car)
(define (tail s) (force (cdr s)))
(define (map-stream proc stream)
(cons (proc (head stream))
(delay (map-stream proc (tail stream)))))
(define (stream-ref stream n)
(if (= n 0)
(head stream)
(stream-ref (tail stream) (- n 1))))
; Printer
(define (pretty-print obj port pos)
(let ((node (pp-prescan obj 0)))
; (if (> (column-of (node-dimensions node)) *line-width*)
; ;; Eventually add a pass to change format of selected combinations
; ;; from big-ell to little-ell.
; (begin (display ";** too wide - ")
; (write (node-dimensions node))
; (newline)))
(print-node node port pos)))
(define make-node list)
(define (node-dimensions node)
((car node)))
(define (node-pass-2 node pos)
((cadr node) pos))
(define (print-node node port pos)
((caddr node) port pos))
(define (pp-prescan obj hang)
(cond ((symbol? obj)
(make-leaf (string-length (symbol->string obj))
obj hang))
((number? obj)
(make-leaf (string-length (number->string obj))
obj hang))
((boolean? obj)
(make-leaf 2 obj hang))
((string? obj)
;;++ Should count number of backslashes and quotes
(make-leaf (+ (string-length obj) 2) obj hang))
((char? obj)
(make-leaf (case obj
((#\space) 7)
((#\newline) 9)
(else 3))
obj hang))
((pair? obj)
(pp-prescan-pair obj hang))
((vector? obj)
(pp-prescan-vector obj hang))
(else
(pp-prescan-random obj hang))))
(define (make-leaf width obj hang)
(let ((width (+ width hang)))
(make-node (lambda () width)
(lambda (pos)
(+ pos width))
(lambda (port pos)
(write obj port)
(do ((i 0 (+ i 1)))
((>= i hang) (+ pos width))
(write-char #\) port))))))
(define (make-prefix-node string node)
(let ((len (string-length string)))
(make-node (lambda ()
(+ (node-dimensions node) len))
(lambda (pos)
(node-pass-2 node (+ pos len)))
(lambda (port pos)
(display string port)
(print-node node port (+ pos len))))))
(define (pp-prescan-vector obj hang)
(if (= (vector-length obj) 0)
(make-leaf 3 obj hang)
(make-prefix-node "#" (pp-prescan-list (vector->list obj) #t hang))))
; Improve later.
(define (pp-prescan-random obj hang)
(let ((l (disclose obj)))
(if (list? l)
(make-prefix-node "#." (pp-prescan-list l #t hang))
(make-leaf 25 obj hang)))) ;Very random number
(define (pp-prescan-pair obj hang)
(cond ((read-macro-inverse obj)
=>
(lambda (inverse)
(make-prefix-node inverse (pp-prescan (cadr obj) hang))))
(else
(pp-prescan-list obj #f hang))))
(define (pp-prescan-list obj random? hang)
(let loop ((l obj) (z '()))
(if (pair? (cdr l))
(loop (cdr l)
(cons (pp-prescan (car l) 0) z))
(make-list-node
(reverse
(if (null? (cdr l))
(cons (pp-prescan (car l) (+ hang 1)) z)
(cons (make-prefix-node ". " (pp-prescan (cdr l) (+ hang 1)))
(cons (pp-prescan (car l) 0) z))))
obj
(or random? (not (null? (cdr l))))))))
; Is it sufficient to tell parent node:
; At a cost of X line breaks, I can make myself narrower by Y columns. ?
; Then how do we decide whether we narrow ourselves or some of our children?
(define (make-list-node node-list obj random?)
(let* ((random? (or random?
;; Heuristic for things like do, cond, let, ...
(not (symbol? (car obj)))
(eq? (car obj) 'else)))
(probe (if (not random?)
(indentation-for (car obj))
#f))
(format horizontal-format)
(dimensions (compute-dimensions node-list format))
(go-non-horizontal
(lambda (col)
(set! format
(cond (random? vertical-format)
(probe (probe obj))
(else big-ell-format)))
(let* ((start-col (+ col 1))
(col (node-pass-2 (car node-list) start-col))
(final-col
(format (cdr node-list)
(lambda (node col target-col)
(node-pass-2 node target-col))
start-col
(+ col 1)
col)))
(set! dimensions (compute-dimensions node-list format))
final-col))))
(if (> dimensions
(if probe
*single-line-special-form-limit*
*line-width*))
(go-non-horizontal 0))
(make-node (lambda () dimensions)
(lambda (col) ;Pass 2: if necessary, go non-horizontal
(let ((defacto (+ col (column-of dimensions))))
(if (> defacto *line-width*)
(go-non-horizontal col)
defacto)))
(lambda (port pos)
(write-char #\( port)
(let* ((pos (+ pos 1))
(start-col (column-of pos))
(pos (print-node (car node-list) port pos)))
(format (cdr node-list)
(lambda (node pos target-col)
(let ((pos (go-to-column target-col
port pos)))
(print-node node port pos)))
start-col
(+ (column-of pos) 1)
pos))))))
(define (compute-dimensions node-list format)
(let* ((start-col 1) ;open paren
(pos (+ (make-position start-col 0)
(node-dimensions (car node-list)))))
(format (cdr node-list)
(lambda (node pos target-col)
(let* ((dims (node-dimensions node))
(lines (+ (line-of pos) (line-of dims)))
(width (+ target-col (column-of dims))))
(if (>= (column-of pos) target-col)
;; Line break required
(make-position
(max (column-of pos) width)
(+ lines 1))
(make-position width lines))))
start-col
(+ (column-of pos) 1) ;first-col
pos)))
; Three positions are significant
; (foo baz ...)
; ^ ^ ^
; | | +--- (column-of pos)
; | +------ first-col
; +---------- start-col
; Separators
(define on-same-line
(lambda (start-col first-col pos)
start-col first-col ;ignored
(+ (column-of pos) 1)))
(define indent-under-first
(lambda (start-col first-col pos)
start-col ;ignored
first-col))
(define indent-for-body
(lambda (start-col first-col pos)
first-col ;ignored
(+ start-col 1)))
(define indent-under-head
(lambda (start-col first-col pos)
first-col ;ignored
start-col))
; Format constructors
(define (once separator format)
(lambda (tail proc start-col first-col pos)
(if (null? tail)
pos
(let ((target-col (separator start-col first-col pos)))
(format (cdr tail)
proc
start-col
first-col
(proc (car tail) pos target-col))))))
(define (indefinitely separator)
(letrec ((self (once separator ;eta
(lambda (tail proc start-col first-col pos)
(self tail proc start-col first-col pos)))))
self))
(define (repeatedly separator count format)
(do ((i 0 (+ i 1))
(format format
(once separator format)))
((>= i count) format)))
; Particular formats
(define vertical-format
(indefinitely indent-under-head))
(define horizontal-format
(indefinitely on-same-line))
(define big-ell-format
(indefinitely indent-under-first))
(define little-ell-format
(indefinitely indent-for-body))
(define format-for-named-let
(repeatedly on-same-line 2 (indefinitely indent-for-body)))
(define hook-formats
(letrec ((stream (cons little-ell-format
(delay (map-stream (lambda (format)
(once indent-under-first format))
stream)))))
stream))
; Hooks for special forms.
; A hook maps an expression to a format.
(define (compute-let-indentation exp)
(if (and (not (null? (cdr exp)))
(symbol? (cadr exp)))
format-for-named-let
(stream-ref hook-formats 1)))
(define hook
(let ((hooks (map-stream (lambda (format)
(lambda (exp) exp ;ignored
format))
hook-formats)))
(lambda (n)
(stream-ref hooks n))))
; Table of indent hooks.
(define indentations (make-table))
(define (indentation-for name)
(table-ref indentations name))
(define (define-indentation name n)
(table-set! indentations
name
(if (integer? n) (hook n) n)))
; Indent hooks for Revised^n Scheme.
(for-each (lambda (name)
(define-indentation name 1))
'(lambda define define-syntax let* letrec let-syntax letrec-syntax
case call-with-values call-with-input-file
call-with-output-file with-input-from-file
with-output-to-file syntax-rules))
(define-indentation 'do 2)
(define-indentation 'call-with-current-continuation 0)
(define-indentation 'let compute-let-indentation)
; Kludge to force vertical printing (do AND and OR as well?)
(define-indentation 'if (lambda (exp) big-ell-format))
(define-indentation 'cond (lambda (exp) big-ell-format))
; Other auxiliaries
(define (go-to-column target-col port pos) ;=> pos
;; Writes at least one space or newline
(let* ((column (column-of pos))
(line (if (>= column target-col)
(+ (line-of pos) 1)
(line-of pos))))
(do ((column (if (>= column target-col)
(begin (newline port) 0)
column)
(+ column 1)))
((>= column target-col)
(make-position column line))
(write-char #\space port))))
(define (make-position column line)
(+ column (* line 1000)))
(define (column-of pos)
(remainder pos 1000))
(define (line-of pos)
(quotient pos 1000))
(define (read-macro-inverse x)
(cond ((and (pair? x)
(pair? (cdr x))
(null? (cddr x)))
(case (car x)
((quote) "'")
((quasiquote) "`")
((unquote) ",")
((unquote-splicing) ",@")
(else #f)))
(else #f)))
; For the command processor:
;(define-command 'p "<exp>" "pretty-print" '(expression)
; (p (eval expression (user-package)) (command-output)))