Skip to content

natefaubion/purescript-dodo-printer

Repository files navigation

purescript-dodo-printer

An adequate printer.

This library implements the equivalent functions of many other pretty-printers in the Wadler/Leijen style but with all the names gratuitously changed.

It also provides nice ANSI integration.

Getting Started

The core type of Dodo is Doc, which represents a document. It has one type parameter which represents an annotation. Annotations let you mark parts of your document tree with things such as styles and colors (e.g. Dodo.Ansi for ANSI graphics). Docs which don't use annotations can use a type variable or Void. The most primitive ways to construct and manipulate Docs are via text and Monoid.

import Prelude
import Dodo (Doc, text)

hello :: forall a. Doc a
hello = text "Hello, " <> text "World!"

Using (<>) (or append, or fold, or any other Monoid function) will put text next to each other on a line. In order to render this, we need to choose a particular printer interface and provide some print options.

The most basic printer is plainText, which only renders plain text, ignoring all annotations. Print options let you configure things like indentation and page width. There are several presets for print options, so we will just pick one for now (twoSpaces).

import Prelude
import Effect (Effect)
import Effect.Console as Console
import Dodo (Doc, text, plainText, twoSpaces, print)

hello :: forall a. Doc a
hello = text "Hello, " <> text "World!"

main :: Effect Unit
main = Console.log $ print plainText twoSpaces hello
Hello, World!

Indentation

Dodo treats indentation as a separate configurable thing. Indentation is only ever a line prefix, and printed on demand after lines breaks and before text. There are two primitive functions for manipulating indentation:

  • indent which increases the indentation level by one indent unit (configurable with print options).
  • align which increases the indentation level by a certain number of spaces (not configurable).

Let's try indenting the second word in our example:

hello :: forall a. Doc a
hello = text "Hello, " <> indent (text "World!")

Printing this, however, will yield an identical result. That's because we already printed text, and indentation only affects the line prefix. We can insert a break:

hello :: forall a. Doc a
hello = text "Hello, " <> indent (break <> text "World!")
Hello,
  World!

We can also align to a certain number of spaces:

hello :: forall a. Doc a
hello = text "Hello, " <> align 10 (break <> text "World!")
Hello,
          World!

Or even align to the current column:

hello :: forall a. Doc a
hello = text "Hello, " <> alignCurrentColumn (break <> text "World!")
Hello,
       World!

Flexible Layouts

We've learned how to append text, insert line breaks, and indent, but often times we want our layout to be a little more flexible. If we have plenty of space left on the line, we may want to print our document on a single line, and only insert breaks as needed. There are two primitives for building flexible layouts:

  • flexGroup which specifies a flexible region of the document and may need to respond to page width constraints.
  • flexAlt which specifies an alternative document to try and render while in a flex group, falling back to a default if it doesn't fit.

When Dodo enters a flex group, it will use the first argument to any flex alternatives, rendering until content either extends past the configured page width or until there is a line break. If it encounters either of these conditions, it will back up and use the second arguments to any flex alternatives.

One of the most basic flexible documents is spaceBreak, which will either insert a space or a line break. spaceBreak is defined as:

spaceBreak :: forall a. Doc a
spaceBreak = flexAlt (text " ") break

If we substitute break for spaceBreak in our example:

hello :: forall a. Doc a
hello = text "Hello," <> indent (spaceBreak <> text "World!")

Nothing will change! Without a surrounding flex group, flex alternatives always render the second argument.

hello :: forall a. Doc a
hello = flexGroup (text "Hello," <> indent (spaceBreak <> text "World!"))
Hello, World!

Since there was available width on the line, it rendered with a space. If we configure our page width to something really small though:

main :: Effect Unit
main = Console.log $ print plainText (twoSpaces { pageWidth = 10 }) hello
Hello,
  World!

Dodo inserts the break and indent. "Hello, World!" on a single line would exceed the maximum page width, so it uses the flex default (break) instead.

Two-Dimensional Layouts

Dodo also supports two-dimensional layouts through the Dodo.Box interface. Boxes can be joined and aligned both vertically and horizontally to create complex layouts such as tables or grids.

Examples