chibi-scheme/lib/chibi/show/pretty.scm
2018-01-07 14:42:52 +09:00

368 lines
12 KiB
Scheme

;; pretty.scm -- pretty printing format combinator
;; Copyright (c) 2006-2018 Alex Shinn. All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; utilities
(define (take* ls n) ; handles dotted lists and n > length
(cond ((zero? n) '())
((pair? ls) (cons (car ls) (take* (cdr ls) (- n 1))))
(else '())))
(define (drop* ls n) ; may return the dot
(cond ((zero? n) ls)
((pair? ls) (drop* (cdr ls) (- n 1)))
(else ls)))
(define (make-space n) (make-string n #\space))
(define (make-nl-space n) (string-append "\n" (make-string n #\space)))
(define (joined/shares fmt ls shares . o)
(let ((sep (displayed (if (pair? o) (car o) " "))))
(fn ()
(if (null? ls)
nothing
(let lp ((ls ls))
(each
(fmt (car ls))
(let ((rest (cdr ls)))
(cond
((null? rest) nothing)
((pair? rest)
(call-with-shared-ref/cdr rest
shares
(fn () (lp rest))
sep))
(else (each sep ". " (fmt rest)))))))))))
(define (string-find/index str pred i)
(string-cursor->index
str
(string-find str pred (string-index->cursor str i))))
(define (try-fitted2 proc fail)
(fn (width output)
(let ((out (open-output-string)))
(call-with-current-continuation
(lambda (abort)
;; Modify output to accumulate to an output string port,
;; and escape immediately with failure if we exceed the
;; column width.
(define (output* str)
(fn (col)
(let lp ((i 0) (col col))
(let ((nli (string-find/index str #\newline i))
(len (string-length str)))
(if (< nli len)
(if (> (+ (- nli i) col) width)
(abort fail)
(lp (+ nli 1) 0))
(let ((col (+ (- len i) col)))
(cond
((> col width)
(abort fail))
(else
(output str)))))))))
(fn-fork
(with ((output output*)
(port out))
proc)
;; fitted successfully
(output (get-output-string out))))))))
(define (try-fitted proc . fail)
(if (null? fail)
proc
(try-fitted2 proc (apply try-fitted fail))))
(define (fits-in-width width proc)
(call-with-current-continuation
(lambda (abort)
(show
#f
(fn (output)
(define (output* str)
(each (output str)
(fn (col)
(if (>= col width)
(abort #f)
nothing))))
(with ((output output*))
proc))))))
(define (fits-in-columns width ls writer)
(let ((max-w (quotient width 2)))
(let lp ((ls ls) (res '()) (widest 0))
(cond
((pair? ls)
(let ((str (fits-in-width max-w (writer (car ls)))))
(and str
(lp (cdr ls)
(cons str res)
(max (string-length str) widest)))))
((null? ls) (cons widest (reverse res)))
(else #f)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; style
(define syntax-abbrevs
'((quote . "'") (quasiquote . "`")
(unquote . ",") (unquote-splicing . ",@")
))
(define (pp-let ls pp shares)
(if (and (pair? (cdr ls)) (symbol? (cadr ls)))
(pp-with-indent 2 ls pp shares)
(pp-with-indent 1 ls pp shares)))
(define indent-rules
`((lambda . 1) (define . 1)
(let . ,pp-let) (loop . ,pp-let)
(let* . 1) (letrec . 1) (letrec* . 1) (and-let* . 1) (let1 . 2)
(let-values . 1) (let*-values . 1) (receive . 2) (parameterize . 1)
(let-syntax . 1) (letrec-syntax . 1) (syntax-rules . 1) (syntax-case . 2)
(match . 1) (match-let . 1) (match-let* . 1)
(if . 3) (when . 1) (unless . 1) (case . 1) (while . 1) (until . 1)
(do . 2) (dotimes . 1) (dolist . 1) (test . 1)
(condition-case . 1) (guard . 1) (rec . 1)
(call-with-current-continuation . 0)
))
(define indent-prefix-rules
`(("with-" . -1) ("call-with-" . -1) ("define-" . 1))
)
(define indent-suffix-rules
`(("-case" . 1))
)
(define (pp-indentation form)
(let ((indent
(cond
((assq (car form) indent-rules) => cdr)
((and (symbol? (car form))
(let ((str (symbol->string (car form))))
(or (find (lambda (rx) (string-prefix? (car rx) str))
indent-prefix-rules)
(find (lambda (rx) (string-suffix? (car rx) str))
indent-suffix-rules))))
=> cdr)
(else #f))))
(if (and (number? indent) (negative? indent))
(max 0 (- (+ (length+ form) indent) 1))
indent)))
(define (with-reset-shares shares proc)
(let ((orig-count (cdr shares)))
(fn ()
(let ((new-count (cdr shares)))
(cond
((> new-count orig-count)
(hash-table-walk
(car shares)
(lambda (k v)
(if (and (cdr v) (>= (car v) orig-count))
(set-cdr! v #f))))
(set-cdr! shares orig-count)))
proc))))
(define (pp-with-indent indent-rule ls pp shares)
(fn ((col1 col))
(each
"("
(pp (car ls))
(fn ((col2 col) width string-width)
(let ((fixed (take* (cdr ls) (or indent-rule 1)))
(tail (drop* (cdr ls) (or indent-rule 1)))
(default
(let ((sep (make-nl-space (+ col1 1))))
(each sep (joined/shares pp (cdr ls) shares sep))))
;; reset in case we don't fit on the first line
(reset-shares (with-reset-shares shares nothing)))
(call-with-output
(each " "
(joined/shares
(lambda (x) (pp-flat x pp shares)) fixed shares " "))
(lambda (first-line)
(cond
((< (+ col2 (string-width first-line)) width)
;; fixed values on first line
(let ((sep (make-nl-space
(if indent-rule (+ col1 2) (+ col2 1)))))
(each first-line
(cond
((not (or (null? tail) (pair? tail)))
(each ". " (pp tail pp shares)))
((> (length+ (cdr ls)) (or indent-rule 1))
(each sep (joined/shares pp tail shares sep)))
(else
nothing)))))
(indent-rule
;; fixed values lined up, body indented two spaces
(try-fitted
(each
reset-shares
" "
(joined/shares pp fixed shares (make-nl-space (+ col2 1)))
(if (pair? tail)
(let ((sep (make-nl-space (+ col1 2))))
(each sep (joined/shares pp tail shares sep)))
nothing))
(each reset-shares default)))
(else
;; all on separate lines
(each reset-shares default)))))))
")")))
(define (pp-app ls pp shares)
(let ((indent-rule (pp-indentation ls)))
(if (procedure? indent-rule)
(indent-rule ls pp shares)
(pp-with-indent indent-rule ls pp shares))))
;; the elements may be shared, just checking the top level list
;; structure
(define (proper-non-shared-list? ls shares)
(let ((tab (car shares)))
(let lp ((ls ls))
(or (null? ls)
(and (pair? ls)
(not (hash-table-ref/default tab ls #f))
(lp (cdr ls)))))))
(define (non-app? x)
(if (pair? x)
(or (not (or (null? (cdr x)) (pair? (cdr x))))
(non-app? (car x)))
(not (symbol? x))))
(define (pp-data-list ls pp shares)
(each
"("
(fn (col width string-width)
(let ((avail (- width col)))
(cond
((and (pair? (cdr ls)) (pair? (cddr ls)) (pair? (cdr (cddr ls)))
(fits-in-columns width ls (lambda (x) (pp-flat x pp shares))))
=> (lambda (ls)
;; at least four elements which can be broken into columns
(let* ((prefix (make-nl-space col))
(widest (+ 1 (car ls)))
(columns (quotient width widest))) ; always >= 2
(let lp ((ls (cdr ls)) (i 1))
(cond
((null? ls)
nothing)
((null? (cdr ls))
(displayed (car ls)))
((>= i columns)
(each (car ls)
prefix
(fn () (lp (cdr ls) 1))))
(else
(let ((pad (- widest (string-width (car ls)))))
(each (car ls)
(make-space pad)
(lp (cdr ls) (+ i 1))))))))))
(else
;; no room, print one per line
(joined/shares pp ls shares (make-nl-space col))))))
")"))
(define (pp-flat x pp shares)
(cond
((pair? x)
(cond
((and (pair? (cdr x)) (null? (cddr x))
(assq (car x) syntax-abbrevs))
=> (lambda (abbrev)
(each (cdr abbrev)
(call-with-shared-ref
(cadr x)
shares
(pp-flat (cadr x) pp shares)))))
(else
(each "("
(joined/shares (lambda (x) (pp-flat x pp shares)) x shares " ")
")"))))
((vector? x)
(each "#("
(joined/shares
(lambda (x) (pp-flat x pp shares)) (vector->list x) shares " ")
")"))
(else
(pp x))))
(define (pp-pair ls pp shares)
(cond
;; one element list, no lines to break
((null? (cdr ls))
(each "(" (pretty (car ls)) ")"))
;; quote or other abbrev
((and (pair? (cdr ls)) (null? (cddr ls))
(assq (car ls) syntax-abbrevs))
=> (lambda (abbrev)
(each (cdr abbrev) (pp (cadr ls)))))
(else
(try-fitted
(fn () (pp-flat ls pp shares))
(with-reset-shares
shares
(fn ()
(if (and (non-app? ls)
(proper-non-shared-list? ls shares))
(pp-data-list ls pp shares)
(pp-app ls pp shares))))))))
(define (pp-vector vec pp shares)
(each "#" (pp-data-list (vector->list vec) pp shares)))
;; adapted from `write-with-shares'
(define (pp obj shares)
(fn (radix precision)
(let ((write-number
(cond
((and (not precision)
(assv radix '((16 . "#x") (10 . "") (8 . "#o") (2 . "#b"))))
=> (lambda (cell)
(lambda (n)
(if (or (exact? n) (eqv? radix 10))
(each (cdr cell) (number->string n (car cell)))
(with ((radix 10)) (numeric n))))))
(else (lambda (n) (with ((radix 10)) (numeric n)))))))
(let pp ((obj obj))
(call-with-shared-ref
obj shares
(fn ()
(cond
((pair? obj)
(pp-pair obj pp shares))
((vector? obj)
(pp-vector obj pp shares))
((number? obj)
(write-number obj))
(else
(write-with-shares obj shares)))))))))
(define (pretty obj)
(fn ()
(call-with-output
(each (pp obj (extract-shared-objects obj #t))
fl)
displayed)))
(define (pretty-shared obj)
(fn ()
(call-with-output
(each (pp obj (extract-shared-objects obj #f))
fl)
displayed)))
(define (pretty-simply obj)
(fn ()
(call-with-output
(each (pp obj (extract-shared-objects #f #f))
fl)
displayed)))