Pretty Printing ASTs with ASDL, and Metaprogramming


Prior to ASDL, oil used hand-written Python classes to represent the shell AST, and ad hoc print() statements to show their debug representation.

Now our ASDL implementation can print an instance of any type automatically. For example, compare:

(original shell source)

The new format is more readable, consistent, and complete. We can also print ASTs in plain text or ANSI-colored text.

Abbreviated AST Format

This program:

echo "hello $name"

is represented with this AST:

  words: [
    (CompoundWord parts:[(LiteralPart token:(token id:Lit_Chars val:echo span_id:0))])
      parts: [
          parts: [
            (LiteralPart token:(token id:Lit_Chars val:"hello " span_id:3))
            (SimpleVarSub token:(token id:VSub_Name val:"$name" span_id:4))

That is, we have a SimpleCommand node with two children:

A span is a subsequence of characters in a line. They're numbered with a span_id from 0 to n-1, and concatenating the spans reproduces the source file exactly. Spans are used for error messages and for automatic conversion of osh source to oil source.

This representation is complete but can be unwieldy, so ASDL has an application-specific hook to abbreviate nodes.

Except for these locations, the abbreviated format has the same information, and is more readable:

(C {(echo)} {(DQ ("hello ") ($ VSub_Name "$name"))})

A partial list of abbreviations:

Pretty Printing Requires Metaprogramming

I don't want to write print statements for every AST node type I define — I want "generic" pretty printing, which works for any type.

As shown above, oil's ASDL implementation now supports this. Notably, it requires treating types as objects. In other words, it requires reflection, a kind of metaprogramming.

The end of the last post outlined some metaprogramming topics. I don't have a big conclusion now, but here are two related thoughts:

So, in a statically-typed language, you have to "jump out of" the language into a meta-language to implement something as basic as printing.

printf() and scanf() are both little languages on top of C, and in fact printf is Turing complete! This is a mistake. I started a Language Design and Theory of Computation page which surveys similar issues. A lot of the hard work was done by Accidentally Turing Complete.


I'm trying to stay on the track I laid out in the last post, so the next post should be an update on the source code size.