Pretty printer (and more) in REPL

It is daunting when you evaluate an expression on REPL and realize the result is a huge S-expr. Especially when you're running gosh inside Emacs with font-lock mode, since Emacs gets crawling trying to parse the huge output.

The reason I don't want to abbreviate the output was that I frequently copy the REPL output and reuse it---with Emacs, copying one S-expr is just a couple of keystrokes, no matter how big it is---but the big output dragging Emacs is irritating, nonetheless.

Gauche had several mechanisms to improve it for long time, but I finally put things together into a usable feature.

Sample session

Let me take you a little tour, for it is easier to see in examples. First, we need some interesting data.

gosh> ,u data.random gosh> ,u gauche.generator gosh> (define word (gmap string->symbol (strings-of (integers-poisson$ 12) (chars$ #[A-Z])))) word gosh> (define leaf? (samples$ '(#t #f #f #f))) leaf? gosh> (define (tree d) (cons (if (or (zero? d) (leaf?)) (word) (tree (- d 1))) (if (or (zero? d) (leaf?)) '() (tree (- d 1))))) tree

(tree N) would generate random nested list of max depth N. You can make several tries to find a reasonable size of the data.

gosh> (tree 5) ((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL RELGWZEGSR)

Looks good, so let's save it.

gosh> (define t *1) t

Now, all the tree in one line is hard to understand. Let's pretty-print it.

gosh> ,pm pretty #t Current print mode: length : 50 level : 10 pretty : #t width : 79 base : 10 radix : #f gosh> t ((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL RELGWZEGSR)

The ,pm toplevel command is an abbreviation of ,print-mode . Yes, setting print mode pretty to #t makes REPL pretty-prints the result.

The pretty printer tries to fit the S-expression within width. You can change it.

gosh> ,pm width 40 Current print mode: length : 50 level : 10 pretty : #t width : 40 base : 10 radix : #f gosh> t ((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL RELGWZEGSR)

It's still too long, so let's limit the length of the printed list:

gosh> ,pm length 3 Current print mode: length : 3 level : 10 pretty : #t width : #f base : 10 radix : #f gosh> t ((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX ....) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ....)

Lists (and vectors) longer than 3 elements are abbreviated using ellipses. You can also limit the number of nesting level:

gosh> ,pm level 3 Current print mode: length : 3 level : 3 pretty : #t width : 40 base : 10 radix : #f gosh> t ((HESUBSPMIBQBBWWZZ (#) NTAQUDHAXX ....) ((UAYIBNNC # QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ....)

The lists nested deeper than the current level are shown as # .

If you need to see everything, e.g. to copy & paste, you can use ,pa toplevel command (shorthand of ,print-all ), which writes previous result without abbreviation.

gosh> ,pa ((HESUBSPMIBQBBWWZZ (((EHMZYLCL) QZKTHLZIKIXS)) NTAQUDHAXX (FMEBQP) PSHRSTW) ((UAYIBNNC (XAPYQBPOHSY) QFIZMITEWULRBMEO)) (WLQITJTZNBO (GJZNEKWBMLGCWKLPN) EINLIRVDLLGPQ) ((HZBDNGYBBQD)) YIQZWPL RELGWZEGSR)

You can also change the default base radix of integers by base. The radix mode switches whether radix prefix ( #b , #x , #nr etc.) should be printed.

gosh> ,pm base 2 Current print mode: length : 3 level : 3 pretty : #t width : #f base : 2 radix : #f gosh> 4753 1001010010001 gosh> ,pm base 16 radix #t Current print mode: length : 3 level : 3 pretty : #t width : 40 base : 16 radix : #t gosh> 4753 #x1291

Now, get back to the default.

gosh> ,pm default Current print mode: length : 50 level : 10 pretty : #f width : 79 base : 10 radix : #f

You may notice that we have length=50 and level=10 as default. This prevents accidentally printing huge S-expression, while most useful data can be printed entirely.

Write controls

Common Lisp has several special (dynamic) variables such as *print-length* and *print-pretty* that affect how print (Scheme's write ) works. Our REPL's print-mode imitates that, but instead of using individual dynamic parameters we have a packaged structure, <write-controls> . A new write-controls can be created by make-write-controls :

gosh> (make-write-controls) #<write-controls (:length #f :level #f :base 10 :radix #f :pretty #f :width #f)> gosh> (make-write-controls :length 10 :base 2) #<write-controls (:length 10 :level #f :base 2 :radix #f :pretty #f :width #f)>

Write controls structure is immutable. If you want a controls that's only slightly different from existing controls, you can use write-controls-copy , to which you can give keyword arguments you want to change:

gosh> (write-controls-copy *1 :pretty #t) #<write-controls (:length 10 :level #f :base 2 :radix #f :pretty #f :width #f)>

Gauche's output procedures such as write or display are extended to accept optional write controls.

Limitations

Currently, the pretty printer only handles lists, vectors and uniform vectors. Other objects (including objects with custom printer) are formatted by the system's default writer, so it is rendered as an unbreakable chunk. Ideally, we'd like to pretty-print such objects as well.

Pretty-printing Scheme code requires more features---it must recognize syntactic keywors and adjust indentation. Such feature will be pretty handy to format result of macro transformation, for example. We're planning to support it eventually.

Tags: 0.9.6, pretty-printing