W3cubDocs

/Elixir 1.5

Inspect.Algebra

A set of functions for creating and manipulating algebra documents.

This module implements the functionality described in “Strictly Pretty” (2000) by Christian Lindig with small additions, like support for String nodes, and a custom rendering function that maximises horizontal space use.

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.format(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/1, 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.format(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.format(doc, 10)
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]

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 explicitly 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

Types

t()

Functions

break()

Returns a document entity with the " " string as break

break(string)

Returns a document entity representing a break based on the given string

color(doc, color_key, opts)

Colors a document if the color_key has a color in the options

concat(docs)

Concatenates a list of documents returning a new document

concat(doc1, doc2)

Concatenates two document entities returning a new document

empty()

Returns a document entity used to represent nothingness

fold_doc(docs, folder_fun)

Folds a list of documents into a document using the given folder function

format(doc, width)

Formats a given document for a given width

glue(doc1, doc2)

Glues two documents together inserting " " as a break between them

glue(doc1, break_string, doc2)

Glues two documents (doc1 and doc2) together inserting the given break break_string between them

group(doc)

Returns a group containing the specified document doc

line(doc1, doc2)

Inserts a mandatory linebreak between two documents

nest(doc, level)

Nests the given document at the given level

space(doc1, doc2)

Inserts a mandatory single space between two documents

surround(left, doc, right)

Surrounds a document with characters

surround_many(left, docs, right, opts, fun, separator \\ ",")

Maps and glues a collection of items

to_doc(term, opts)

Converts an Elixir term to an algebra document according to the Inspect protocol

Types

t()

t ::
  :doc_nil |
  :doc_line |
  doc_cons |
  doc_nest |
  doc_break |
  doc_group |
  doc_color |
  binary

Functions

break()

break() :: doc_break

Returns a document entity with the " " string as break.

See break/1 for more information.

break(string)

break(binary) :: doc_break

Returns a document entity representing a break based on the given string.

This break can be rendered as a linebreak or as the given string, depending on the mode of the chosen layout or the provided separator.

Examples

Let’s create a document by concatenating two strings with a break between them:

iex> doc = Inspect.Algebra.concat(["a", Inspect.Algebra.break("\t"), "b"])
iex> Inspect.Algebra.format(doc, 80)
["a", "\t", "b"]

Notice the break was represented with the given string, because we didn’t reach a line limit. Once we do, it is replaced by a newline:

iex> break = Inspect.Algebra.break("\t")
iex> doc = Inspect.Algebra.concat([String.duplicate("a", 20), break, "b"])
iex> Inspect.Algebra.format(doc, 10)
["aaaaaaaaaaaaaaaaaaaa", "\n", "b"]

color(doc, color_key, opts)

color(t, Inspect.Opts.color_key, Inspect.Opts.t) :: doc_color

Colors a document if the color_key has a color in the options.

concat(docs)

concat([t]) :: t

Concatenates a list of documents returning a new document.

Examples

iex> doc = Inspect.Algebra.concat(["a", "b", "c"])
iex> Inspect.Algebra.format(doc, 80)
["a", "b", "c"]

concat(doc1, doc2)

concat(t, t) :: t

Concatenates two document entities returning a new document.

Examples

iex> doc = Inspect.Algebra.concat("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"]

empty()

empty() :: :doc_nil

Returns a document entity used to represent nothingness.

Examples

iex> Inspect.Algebra.empty
:doc_nil

fold_doc(docs, folder_fun)

fold_doc([t], (t, t -> t)) :: t

Folds a list of documents into a document using the given folder function.

The list of documents is folded “from the right”; in that, this function is similar to List.foldr/3, except that it doesn’t expect an initial accumulator and uses the last element of docs as the initial accumulator.

Examples

iex> docs = ["A", "B", "C"]
iex> docs = Inspect.Algebra.fold_doc(docs, fn(doc, acc) ->
...>   Inspect.Algebra.concat([doc, "!", acc])
...> end)
iex> Inspect.Algebra.format(docs, 80)
["A", "!", "B", "!", "C"]

format(doc, width)

format(t, non_neg_integer | :infinity) :: iodata

Formats a given document for a given width.

Takes the maximum width and a document to print as its arguments and returns an IO data representation of the best layout for the document to fit in the given width.

Examples

iex> doc = Inspect.Algebra.glue("hello", " ", "world")
iex> Inspect.Algebra.format(doc, 30) |> IO.iodata_to_binary()
"hello world"
iex> Inspect.Algebra.format(doc, 10) |> IO.iodata_to_binary()
"hello\nworld"

glue(doc1, doc2)

glue(t, t) :: t

Glues two documents together inserting " " as a break between them.

This means the two documents will be separated by " " in case they fit in the same line. Otherwise a line break is used.

Examples

iex> doc = Inspect.Algebra.glue("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", " ", "world"]

glue(doc1, break_string, doc2)

glue(t, binary, t) :: t

Glues two documents (doc1 and doc2) together inserting the given break break_string between them.

For more information on how the break is inserted, see break/1.

Examples

iex> doc = Inspect.Algebra.glue("hello", "\t", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", "\t", "world"]

group(doc)

group(t) :: doc_group

Returns a group containing the specified document doc.

Documents in a group are attempted to be rendered together to the best of the renderer ability.

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.format(doc, 80)
["Hello,", " ", "A", " ", "B"]
iex> Inspect.Algebra.format(doc, 6)
["Hello,", "\n", "A", " ", "B"]

line(doc1, doc2)

line(t, t) :: t

Inserts a mandatory linebreak between two documents.

Examples

iex> doc = Inspect.Algebra.line("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 80)
["Hughes", "\n", "Wadler"]

nest(doc, level)

nest(t, non_neg_integer) :: doc_nest

Nests the given document at the given level.

Nesting will be appended to the line breaks.

Examples

iex> doc = Inspect.Algebra.nest(Inspect.Algebra.glue("hello", "world"), 5)
iex> Inspect.Algebra.format(doc, 5)
["hello", "\n     ", "world"]

space(doc1, doc2)

space(t, t) :: t

Inserts a mandatory single space between two documents.

Examples

iex> doc = Inspect.Algebra.space("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 5)
["Hughes", " ", "Wadler"]

surround(left, doc, right)

surround(t, t, t) :: t

Surrounds a document with characters.

Puts the given document doc between the left and right documents 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.format(doc, 3)
["[", "a", "\n ", "b", "]"]

surround_many(left, docs, right, opts, fun, separator \\ ",")

surround_many(t, [any], t, Inspect.Opts.t, (term, Inspect.Opts.t -> t), t) :: t

Maps and glues a collection of items.

It uses the given left and right documents as surrounding and the separator document separator to separate items in docs. A limit can be passed: when this limit is reached, this function stops gluing and outputs "..." instead.

Examples

iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...>         %Inspect.Opts{limit: :infinity}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 5) |> IO.iodata_to_binary
"[1,\n 2,\n 3,\n 4,\n 5]"

iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...>         %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end)
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1, 2, 3, ...]"

iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]",
...>         %Inspect.Opts{limit: 3}, fn i, _opts -> to_string(i) end, "!")
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary
"[1! 2! 3! ...]"

to_doc(term, opts)

to_doc(any, Inspect.Opts.t) :: t

Converts an Elixir term to an algebra document according to the Inspect protocol.

© 2012–2017 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.5.0/Inspect.Algebra.html