# `Inspect.Algebra`
[🔗](https://github.com/elixir-lang/elixir/blob/v1.20.0-rc.3/lib/elixir/lib/inspect/algebra.ex#L191)

A set of functions for creating and manipulating algebra
documents.

This module implements the functionality described in
["Strictly Pretty" (2000) by Christian Lindig][0] with small
additions, like support for binary nodes and a break mode that
maximises use of horizontal space.

    iex> Inspect.Algebra.line()
    :doc_line

    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 `glue/3` and
`group/1`. A glue inserts a break between two documents. A group
indicates a document that must fit the current line, otherwise
breaks are rendered as new lines. Let's glue two docs together
with a break, group it and then render it:

    iex> doc = Inspect.Algebra.glue("a", " ", "b")
    iex> doc = Inspect.Algebra.group(doc)
    iex> Inspect.Algebra.format(doc, 80)
    "a b"

Note that 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> doc = Inspect.Algebra.group(doc)
    iex> Inspect.Algebra.format(doc, 10)
    "aaaaaaaaaaaaaaaaaaaa\nb"

This module uses the byte size to compute how much space there is
left. If your document contains strings, then those need to be
wrapped in `string/1`, which then relies on `String.length/1` to
precompute the document size.

Finally, this module also contains Elixir related functions, a bit
tied to Elixir formatting, such as `to_doc/2`.

## Implementation details

The implementation of `Inspect.Algebra` is based on the Strictly Pretty
paper by [Lindig][0] which builds on top of previous pretty printing
algorithms but is tailored to strict languages, such as Elixir.
The core idea in the paper is the use of explicit document groups which
are rendered as flat (breaks as spaces) or as break (breaks as newlines).

This implementation provides two types of breaks: `:strict` and `:flex`.
When a group does not fit, all strict breaks are treated as newlines.
Flex breaks, however, are re-evaluated on every occurrence and may still
be rendered flat. See `break/1` and `flex_break/1` for more information.

This implementation also adds `force_unfit/1` and optimistic/pessimistic
groups which give more control over the document fitting.

  [0]: https://lindig.github.io/papers/strictly-pretty-2000.pdf

# `is_doc`
*macro* 

# `container_opts`

```elixir
@type container_opts() :: [separator: String.t(), break: :strict | :flex | :maybe]
```

Options for container documents.

# `t`

```elixir
@type t() ::
  binary()
  | doc_nil()
  | doc_cons()
  | doc_line()
  | doc_break()
  | doc_collapse()
  | doc_color()
  | doc_fits()
  | doc_force()
  | doc_group()
  | doc_nest()
  | doc_string()
  | doc_limit()
```

# `break`

```elixir
@spec break(binary()) :: doc_break()
```

Returns a break document 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.

## 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\tb"

Note that 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> doc = Inspect.Algebra.group(doc)
    iex> Inspect.Algebra.format(doc, 10)
    "aaaaaaaaaaaaaaaaaaaa\nb"

# `collapse_lines`
*since 1.6.0* 

```elixir
@spec collapse_lines(pos_integer()) :: doc_collapse()
```

Collapse any new lines and whitespace following this
node, emitting up to `max` new lines.

# `color`
*since 1.18.0* 

```elixir
@spec color(t(), binary()) :: t()
```

Colors a document with the given color (preceding the document itself).

# `color`

> This function is deprecated. Use color_doc/3 instead.

# `color_doc`
*since 1.18.0* 

```elixir
@spec color_doc(t(), Inspect.Opts.color_key(), Inspect.Opts.t()) :: t()
```

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

# `concat`

```elixir
@spec 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)
    "abc"

# `concat`

```elixir
@spec 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)
    "helloworld"

# `container_doc`
*since 1.6.0* 

```elixir
@spec container_doc(
  t(),
  [term()],
  t(),
  Inspect.Opts.t(),
  (term(), Inspect.Opts.t() -&gt; t()),
  container_opts()
) :: t()
```

Wraps `collection` in `left` and `right` according to limit and contents
and returns only the container document.

In practice, one must prefer to use `container_doc_with_opts/6`
over this function, as `container_doc_with_opts/6` returns the
updated options from inspection.

# `container_doc_with_opts`
*since 1.19.0* 

```elixir
@spec container_doc_with_opts(
  t(),
  [term()],
  t(),
  Inspect.Opts.t(),
  (term(), Inspect.Opts.t() -&gt; t()),
  container_opts()
) :: {t(), Inspect.Opts.t()}
```

Wraps `collection` in `left` and `right` according to limit and contents.

It uses the given `left` and `right` documents as surrounding and the
separator document `separator` to separate items in `docs`. If all entries
in the collection are simple documents (texts or strings), then this function
attempts to put as much as possible on the same line. If they are not simple,
only one entry is shown per line if they do not fit.

The limit in the given `inspect_opts` is respected and when reached this
function stops processing and outputs `"..."` instead.

It returns a tuple with the algebra document and the updated options.

## Options

  * `:separator` - the separator used between each doc
  * `:break` - If `:strict`, always break between each element. If `:flex`,
    breaks only when necessary. If `:maybe`, chooses `:flex` only if all
    elements are text-based, otherwise is `:strict`

## Examples

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

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

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

# `empty`

```elixir
@spec empty() :: doc_nil()
```

Returns a document entity used to represent nothingness.

## Examples

    iex> Inspect.Algebra.empty()
    []

# `flex_break`
*since 1.6.0* 

```elixir
@spec flex_break(binary()) :: doc_break()
```

Returns a flex break document based on the given `string`.

A flex break still causes a group to break, like `break/1`,
but it is re-evaluated when the documented is rendered.

For example, take a group document represented as `[1, 2, 3]`
where the space after every comma is a break. When the document
above does not fit a single line, all breaks are enabled,
causing the document to be rendered as:

    [1,
     2,
     3]

However, if flex breaks are used, then each break is re-evaluated
when rendered, so the document could be possible rendered as:

    [1, 2,
     3]

Hence the name "flex". they are more flexible when it comes
to the document fitting. On the other hand, they are more expensive
since each break needs to be re-evaluated.

This function is used by `container_doc/6` and friends to the
maximum number of entries on the same line.

# `flex_glue`
*since 1.6.0* 

```elixir
@spec flex_glue(t(), binary(), t()) :: t()
```

Glues two documents (`doc1` and `doc2`) inserting a
`flex_break/1` given by `break_string` between them.

This function is used by `container_doc/6` and friends
to the maximum number of entries on the same line.

# `fold`
*since 1.18.0* 

```elixir
@spec fold([t()], (t(), t() -&gt; 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(docs, fn doc, acc ->
    ...>     Inspect.Algebra.concat([doc, "!", acc])
    ...>   end)
    iex> Inspect.Algebra.format(docs, 80)
    "A!B!C"

# `fold_doc`

> This function is deprecated. Use fold/2 instead.

# `force_unfit`
*since 1.6.0* 

```elixir
@spec force_unfit(t()) :: doc_force()
```

Forces the current group to be unfit.

# `format`

```elixir
@spec 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.

The document starts flat (without breaks) until a group is found.

## Examples

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

# `glue`

```elixir
@spec glue(t(), binary(), t()) :: t()
```

Glues two documents (`doc1` and `doc2`) 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"

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

# `group`

```elixir
@spec group(t(), :normal | :optimistic | :pessimistic) :: 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. If there are `break/1`s
in the group and the group does not fit the given width,
the breaks are converted into lines. Otherwise the breaks
are rendered as text based on their string contents.

There are three types of groups, described next.

## Group modes

  * `:normal` - the group fits if it fits within the given width

  * `:optimistic` - the group fits if it fits within the given
    width. However, when nested within another group, the parent
    group will assume this group fits as long as it has a single
    break, even if the optimistic group has a `force_unfit/1`
    document within it. Overall, this has an effect similar
    to swapping the order groups break. For example, if you have
    a `parent_group(child_group)` and they do not fit, the parent
    converts breaks into newlines first, allowing the child to compute
    if it fits. However, if the child group is optimistic and it
    has breaks, then the parent assumes it fits, leaving the overall
    fitting decision to the child

  * `:pessimistic` - the group fits if it fits within the given
    width. However it disables any optimistic group within it

## 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,\nA\nB"

## Mode examples

The different groups modes are used by Elixir's code formatter
to avoid breaking code at some specific locations. For example,
consider this code:

    some_function_call(%{..., key: value, ...})

Now imagine that this code does not fit its line. The code
formatter introduces breaks inside `(` and `)` and inside
`%{` and `}`, each within their own group. Therefore the
document would break as:

    some_function_call(
      %{
        ...,
        key: value,
        ...
      }
    )

To address this, the formatter marks the inner group as optimistic.
This means the first group, which is `(...)` will consider the document
fits and avoids adding breaks around the parens. So overall the code
is formatted as:

    some_function_call(%{
      ...,
      key: value,
      ...
    })

# `line`
*since 1.6.0* 

```elixir
@spec line() :: t()
```

A mandatory linebreak.

A group with linebreaks will fit if all lines in the group fit.

## Examples

    iex> doc =
    ...>   Inspect.Algebra.concat(
    ...>     Inspect.Algebra.concat(
    ...>       "Hughes",
    ...>       Inspect.Algebra.line()
    ...>     ),
    ...>     "Wadler"
    ...>   )
    iex> Inspect.Algebra.format(doc, 80)
    "Hughes\nWadler"

# `line`

```elixir
@spec line(t(), t()) :: t()
```

Inserts a mandatory linebreak between two documents.

See `line/0`.

## Examples

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

# `nest`

```elixir
@spec nest(t(), non_neg_integer() | :cursor | :reset, :always | :break) ::
  doc_nest() | t()
```

Nests the given document at the given `level`.

If `level` is an integer, that's the indentation appended
to line breaks whenever they occur. If the level is `:cursor`,
the current position of the "cursor" in the document becomes
the nesting. If the level is `:reset`, it is set back to 0.

`mode` can be `:always`, which means nesting always happen,
or `:break`, which means nesting only happens inside a group
that has been broken.

## Examples

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

# `next_break_fits`

> This function is deprecated. Pass the optimistic/pessimistic type to group/2 instead.

```elixir
@spec next_break_fits(t(), :enabled | :disabled) :: doc_fits()
```

Considers the next break as fit.

# `no_limit`
*since 1.14.0* 

```elixir
@spec no_limit(t()) :: t()
```

Disable any rendering limit while rendering the given document.

## Examples

    iex> doc = Inspect.Algebra.glue("hello", "world") |> Inspect.Algebra.group()
    iex> Inspect.Algebra.format(doc, 10)
    "hello\nworld"
    iex> doc = Inspect.Algebra.no_limit(doc)
    iex> Inspect.Algebra.format(doc, 10)
    "hello world"

# `space`

```elixir
@spec 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"

# `string`
*since 1.6.0* 

```elixir
@spec string(String.t()) :: doc_string()
```

Creates a document represented by string.

While `Inspect.Algebra` accepts binaries as documents,
those are counted by binary size. On the other hand,
`string` documents are measured in terms of graphemes
towards the document size.

## Examples

The following document has 10 bytes and therefore it
does not format to width 9 without breaks:

    iex> doc = Inspect.Algebra.glue("olá", " ", "mundo")
    iex> doc = Inspect.Algebra.group(doc)
    iex> Inspect.Algebra.format(doc, 9)
    "olá\nmundo"

However, if we use `string`, then the string length is
used, instead of byte size, correctly fitting:

    iex> string = Inspect.Algebra.string("olá")
    iex> doc = Inspect.Algebra.glue(string, " ", "mundo")
    iex> doc = Inspect.Algebra.group(doc)
    iex> Inspect.Algebra.format(doc, 9)
    "olá mundo"

# `to_doc`

```elixir
@spec to_doc(any(), Inspect.Opts.t()) :: t()
```

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

In practice, one must prefer to use `to_doc_with_opts/2`
over this function, as `to_doc_with_opts/2` returns the
updated options from inspection.

# `to_doc_with_opts`
*since 1.19.0* 

```elixir
@spec to_doc_with_opts(any(), Inspect.Opts.t()) :: {t(), Inspect.Opts.t()}
```

Converts an Elixir term to an algebra document
according to the `Inspect` protocol, alongside the updated options.

This function is used when implementing the inspect protocol for
a given type and you must convert nested terms to documents too.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
