Inspect.Algebra
A set of functions for creating and manipulating algebra documents, as described in "Strictly Pretty" (2000) by Christian Lindig.
An algebra document is represented by an Inspect.Algebra node
or a regular string.
iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"
With the functions in this module, we can concatenate different elements together and render them:
iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"
The functions nest/2, space/2 and line/2 help you put the
document together into a rigid structure. However, the document
algebra gets interesting when using functions like break/2, which
converts the given string into a line break depending on how much space
there is to print. Let's glue two docs together with a break and then
render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, namely surround/3 and surround_many/5.
Implementation details
The original Haskell implementation of the algorithm by Wadler
relies on lazy evaluation to unfold document groups on two alternatives:
:flat (breaks as spaces) and :break (breaks as newlines).
Implementing the same logic in a strict language such as Elixir leads
to an exponential growth of possible documents, unless document groups
are encoded explictly as :flat or :break. Those groups are then reduced
to a simple document, where the layout is already decided, per Lindig.
This implementation slightly changes the semantic of Lindig's algorithm
to allow elements that belong to the same group to be printed together
in the same line, even if they do not fit the line fully. This was achieved
by changing :break to mean a possible break and :flat to force a flat
structure. Then deciding if a break works as a newline is just a matter
of checking if we have enough space until the next break that is not
inside a group (which is still flat).
Custom pretty printers can be implemented using the documents returned by this module and by providing their own rendering functions.
Summary
| break() | |
| break(s) | Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
|
| concat(docs) | Concatenates a list of documents |
| concat(x, y) | Concatenates two document entities. Takes two arguments: left doc and right doc. Returns a DocCons doc |
| empty() | Returns |
| folddoc(list1, f) | Folds a list of document entities into a document entity using a function that is passed as the first argument |
| glue(x, y) | Inserts a break between two docs. See |
| glue(x, g, y) | Inserts a break, passed as the second argument, between two docs, the first and the third arguments |
| group(d) | Returns a group containing the specified document |
| line(x, y) | Inserts a mandatory linebreak between two document entities |
| nest(x, i) | Nests document entity |
| pretty(d, w) | The pretty printing function |
| space(x, y) | Inserts a mandatory single space between two document entities |
| surround(left, doc, right) | Surrounds a document with characters |
| surround_many(left, docs, right, limit, fun, separator \\ @surround_separator) | Maps and glues a collection of items together using the given separator and surrounds them. A limit can be passed which, once reached, stops gluing and outputs "..." instead |
| to_doc(arg, opts) | Converts an Elixir structure to an algebra document according to the inspect protocol |
Functions
Specs:
- break(binary) :: doc_break_t
Document entity representing a break. This break can
be rendered as a linebreak or as spaces, depending on the
mode of the chosen layout or the provided separator.
Examples
Let's glue two docs together with a break and then render it:
iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"
Notice the break was represented as is, because we haven't reached a line limit. Once we do, it is replaced by a newline:
iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
Specs:
Concatenates two document entities. Takes two arguments: left doc and right doc. Returns a DocCons doc
Examples
iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
Specs:
- empty :: :doc_nil
Returns :doc_nil which is a document entity used to represent
nothingness. Takes no arguments.
Examples
iex> Inspect.Algebra.empty
:doc_nil
Specs:
Folds a list of document entities into a document entity using a function that is passed as the first argument.
Examples
iex> doc = ["A", "B"]
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...> Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
Specs:
Inserts a break, passed as the second argument, between two docs, the first and the third arguments.
Specs:
- group(t) :: doc_group_t
Returns a group containing the specified document.
Examples
iex> doc = Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> Inspect.Algebra.group(
...> Inspect.Algebra.concat(
...> "Hello,",
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "A"
...> )
...> )
...> ),
...> Inspect.Algebra.concat(
...> Inspect.Algebra.break,
...> "B"
...> )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
Specs:
Inserts a mandatory linebreak between two document entities.
Examples
iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
Specs:
- nest(t, non_neg_integer) :: doc_nest_t
Nests document entity x positions deep. Nesting will be
appended to the line breaks.
Examples
iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
Specs:
- pretty(t, non_neg_integer | :infinity) :: binary
The pretty printing function.
Takes the maximum width and a document to print as its arguments and returns the string representation of the best layout for the document to fit in the given width.
Specs:
Inserts a mandatory single space between two document entities.
Examples
iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
Specs:
Surrounds a document with characters.
Puts the document between left and right enclosing and nesting it. The document is marked as a group, to show the maximum as possible concisely together.
Examples
iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
Specs:
Maps and glues a collection of items together using the given separator and surrounds them. A limit can be passed which, once reached, stops gluing and outputs "..." instead.
Examples
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"
iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
"[1! 2! 3! ...]"
Specs:
- to_doc(any, Inspect.Opts.t) :: t
Converts an Elixir structure to an algebra document according to the inspect protocol.