Elixir v1.4.5 Inspect.Algebra View Source
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.
Link to this section Summary
Functions
Returns a document entity representing the default break
Returns a document entity representing a break based on the given
string
Colors a document if the color_key
has a color in the options
Concatenates a list of documents returning a new document
Concatenates two document entities returning a new document
Returns a document entity used to represent nothingness
Folds a list of documents into a document using the given folder function
Formats a given document for a given width
Glues two documents together inserting the default break between them
Glues two documents (doc1
and doc2
) together inserting the given
break break_string
between them
Returns a group containing the specified document doc
Inserts a mandatory linebreak between two documents
Nests the given document at the given level
Inserts a mandatory single space between two documents
Surrounds a document with characters
Maps and glues a collection of items
Converts an Elixir term to an algebra document
according to the Inspect
protocol
Link to this section Types
t :: :doc_nil | :doc_line | doc_cons | doc_nest | doc_break | doc_group | doc_color | binary
Link to this section Functions
Returns a document entity representing the default break.
Same as calling break/1
with the default 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(t, Inspect.Opts.color_key, Inspect.Opts.t) :: doc_color
Colors a document if the color_key
has a color in the options.
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"]
Concatenates two document entities returning a new document.
Examples
iex> doc = Inspect.Algebra.concat("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"]
Returns a document entity used to represent nothingness.
Examples
iex> Inspect.Algebra.empty
:doc_nil
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(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"
Glues two documents together inserting the default break between them.
The break that is inserted between left
and right
is the one returned by
break/0
.
Examples
iex> doc = Inspect.Algebra.glue("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", " ", "world"]
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", " ", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", " ", "world"]
Returns a group containing the specified document doc
.
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"]
Inserts a mandatory linebreak between two documents.
Examples
iex> doc = Inspect.Algebra.line("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 80)
["Hughes", "\n", "Wadler"]
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"]
Inserts a mandatory single space between two documents.
Examples
iex> doc = Inspect.Algebra.space("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 5)
["Hughes", " ", "Wadler"]
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(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! ...]"
Converts an Elixir term to an algebra document
according to the Inspect
protocol.