During the past few weeks, I've been working on prettier, which is a JavaScript pretty printer. We are approaching the phase where we can actually use it so this is a good time to explain how it works.
We're going to go through an example
if (!pretty) { makePretty() } |
String -> AST
The first step is to take this string that represents some JavaScript and to parse it in order to get an AST out of it. An AST is a tree that represents the program. Using either Babylon or Flow we can parse this example and we get the following tree.
Program IfStatement UnaryExpression(!) Identifier(pretty) BlockStatement({}) ExpressionStatement CallExpression Identifier(makePretty) |
You can explore the full AST using astexplorer.net.
AST -> IR
Now that we have this tree, we want to print it. For each type of node like IfStatement, UnaryExpression... we're going to output something. In the case of prettier, this something is an intermediate representation called a document as described by the paper a prettier printer by Philip Wadler.
[ group([ "if (", group([ indent(2, [ softline, "!", "pretty" ]), softline ]), ")", " ", "{", indent(2, [ hardline, "makePretty", "()", ";" ]), hardline, "}" ]), hardline ]; |
You can play around with this representation on the prettier explorer.
IR -> String
The interesting thing about this representation is that it is the same no matter what the line-length is. The basic idea is that the primitives such as group
, indent
, softline
encode the way they should look if they fit in the line or if they don't.
The most important primitive is group
. The algorithm will first try to recursively print a group
on a single line. If it doesn't fit the desired width, then it's going to break the outer group and keep going.
Then, we have primitives that behave differently if they are in a group that fits a single line or not: softline
that does not print anything if the group it is contained in fits and a line otherwise. indent
adds a level of indentation if it doesn't fit. If you are curious, you can look at the short list of available commands.
So, we just need to take this IR, send it through a solver along with the desired line width and we get the result!
Conclusion
Hopefully this gives you a better idea of how a pretty printer that takes into account the desired width work.