Pretty Expressive Printer

A Gren implementation of the Pretty Expressive Printer.

This library implements an optimal pretty printer. Given a page-width limit and a cost model, it searches all possible layouts and selects the one with the lowest cost. Documents are constructed from combinators and rendered with prettyFormat or prettyFormatInfo.

Basic usage:

import PrettyExpressive as P
import PrettyExpressive.Builder as B

-- Create a CostFactory, for measuring optimality ("prettiness")
cf : P.CostFactory P.DefaultCostTuple
cf =
    P.defaultCostFactory { pageWidth = 80, computationWidth = Nothing }

-- Create the document (sequences of pretty-printing directives)
document : P.Doc cost
document =
    P.text "hello" |> P.concat (P.text " world")

-- Render the document to a String. Entry points take a `Builder`;
-- wrap a pure Doc with `B.return` when sharing is not needed.
printed : Maybe String
printed =
    P.prettyFormat cf (B.return document)

Sharing sub-documents: When the same sub-document is referenced from multiple parents — typically two branches of a choice — the renderer can resolve it once per (column, indent) position and reuse the result. OCaml gets this for free from value identity; Gren has no equivalent, so sharing is opt-in via share. share : Doc cost -> Builder (Doc cost) stamps a unique id onto a sub-document; every reference participates in the renderer's memo cache.

sharedExample : B.Builder (P.Doc P.DefaultCostTuple)
sharedExample =
    P.share (P.text "exit();")
        |> B.map
            (\exitD ->
                -- Two references → one resolution per (c, i).
                P.choice
                    (P.concat P.space exitD)
                    (P.nest 4 (P.concat P.nl exitD))
            )

For documents with no repeated sub-trees, B.return document is all you need and the renderer behaves identically to a tree walk. See the docs in PrettyExpressive for the full Builder API and when sharing pays off.

Notes

The ACM article describing the algorithm presents only a few Doc constructors. They are the most important:

  • text
  • nl
  • concat
  • nest
  • group

In the article, the CostFactory has a very small, simple interface.

The OCaml implementation provides many more constructors, and its CostFactory has a much more complicated interface, to support these new constructors. This Gren package provides all the same constructors as the OCaml version does, and has the same more-complicated CostFactory interface.

This Gren package also adds another constructor, for convenience:

  • concat_array

References

Other implementations