2017/08/29
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
Frank (2017/11/13 19:33:01):
shiro (2017/11/16 08:03:41):
Frank (2018/03/06 05:47:46):