A Java implementation of Philip Wadler's "A prettier printer", a pretty-printing algorithm for laying out hierarchical documents as text.
This algorithm is particularly suitable for formatting source code (see for example Prettier).
prettier4j is published for Java 8 and above.
Gradle (build.gradle / build.gradle.kts):
implementation("com.opencastsoftware:prettier4j:0.2.0")
Maven (pom.xml):
<dependency>
<groupId>com.opencastsoftware</groupId>
<artifactId>prettier4j</artifactId>
<version>0.2.0</version>
</dependency>
To render documents using this library you must use com.opencastsoftware.prettier4j.Doc
.
In order to create documents, check out the static methods of that class, especially:
empty()
- creates an emptyDoc
.text(String)
- creates aDoc
from aString
. These are used as the atomic text nodes of a document.
To render documents, the render(int)
instance method is provided. The argument to this method declares a target line width when laying out the document.
It's not always possible for documents to fit within this target width. For example, a single Doc.text
node may be longer than the target width if the argument String
is long enough.
To concatenate documents, the append(Doc)
instance method and related methods providing different separators are provided.
As a general rule, the best way to construct documents using this algorithm is to construct your document by concatenating text nodes, while declaring each place where a line break could be added if necessary.
The lineOrSpace()
, lineOrEmpty()
and related static methods are used to declare line breaks which may be replaced with alternative content if the current Doc
is flattened.
The line()
static method creates a line break which may not be flattened.
However, none of these primitives create flattened layouts on their own.
In order to declare how documents can be flattened, you must declare groups within a document.
For example, the following documents each render to the same content:
Doc.text("one")
.appendLineOrSpace(Doc.text("two"))
.appendLineOrSpace(Doc.text("three"))
.render(30);
// ===> "one\ntwo\nthree"
Doc.text("one")
.appendLineOrSpace(Doc.text("two"))
.appendLineOrSpace(Doc.text("three"))
.render(5);
// ===> "one\ntwo\nthree"
However, if we declare each of those documents as a group using the static method group(Doc)
, they are rendered differently:
Doc.group(
Doc.text("one")
.appendLineOrSpace(Doc.text("two"))
.appendLineOrSpace(Doc.text("three")))
.render(30);
// ===> "one two three"
Doc.group(
Doc.text("one")
.appendLineOrSpace(Doc.text("two"))
.appendLineOrSpace(Doc.text("three")))
.render(5);
// ===> "one\ntwo\nthree"
By declaring a group, we have specified that the contents of each group can be flattened onto a single line if there is enough space.
As a result, the first call to render(int)
renders a space-separated list, whereas the second call renders as a newline separated list. The width of 5 characters provided to the render method in the second call does not allow enough space for the entire group to render on a single line.
The code in this repository is a pretty direct port of the paper's Haskell code to Java.
However, the names relating to line breaks (lineOrSpace
, lineOrEmpty
etc.) in this project are inspired by those used in typelevel/paiges, an excellent Scala port of the same algorithm.
All code in this repository is licensed under the Apache License, Version 2.0. See LICENSE.