Gauche Devlog

< A heads-up for an incompatible fix in util.match | Rounding 1.15 >

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

Past comment(s)

Frank (2017/11/13 19:33:01):

These are interesting features. I just start to look into Gauche for scripting tasks, after using Bigloo when executables are helpful, Kawa when JVM integration is required or CL/SBCL when size doesn't matter.

So thanks a lot for your great work on Gauche.

shiro (2017/11/16 08:03:41):

Actually, there's a way to produce stand-alone executable Gauche applications (I'm using it at work to distribute binaries). The build process hasn't been streamlined, but I hope I can clean it up and document it soon.

Frank (2018/03/06 05:47:46):

This is good to here. I can already sneak in some Scheme here and there at work when using Kawa in Java. Bigloo is quite bare bones, so not the typical scripting environment, but being able to bundle all the features of Gauche into one executable for exactly such personal helpers would extend Scheme reach further. So thanks again!

Post a comment

Name: