查看源代码 Inspect.Algebra (Elixir v1.16.2)

一组用于创建和操作代数文档的函数。

此模块实现了 Christian Lindig 的“Strictly Pretty”(2000 年) 中描述的功能,并进行了一些小的添加,例如对二进制节点的支持和一种最大化使用水平空间的断点模式。

iex> Inspect.Algebra.empty()
:doc_nil

iex> "foo"
"foo"

使用此模块中的函数,我们可以将不同的元素连接在一起并渲染它们

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

函数 nest/2space/2line/2 可以帮助您将文档整理成一个严格的结构。但是,代数文档在使用像 glue/3group/1 这样的函数时会变得有趣。粘合剂在两个文档之间插入一个断点。分组表示必须适合当前行的文档,否则断点将渲染为新行。让我们将两个文档粘合在一起,并在它们之间插入一个断点,将其分组,然后渲染它

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

请注意,断点按原样表示,因为我们还没有达到行限制。一旦我们这样做,它将被替换为换行符

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

此模块使用字节大小来计算剩余空间。如果您的文档包含字符串,那么这些字符串需要用 string/1 包裹,然后它依赖于 String.length/1 来预先计算文档大小。

最后,此模块还包含与 Elixir 相关的函数,这些函数与 Elixir 格式化相关,例如 to_doc/2

实现细节

Inspect.Algebra 的实现基于 Lindig 的“Strictly Pretty”论文,该论文建立在以前的漂亮打印算法之上,但专门针对像 Elixir 这样的严格语言。论文中的核心思想是使用显式的文档组,这些文档组可以渲染为平面(断点作为空格)或断点(断点作为换行符)。

此实现提供了两种类型的断点::strict:flex。当一个组不合适时,所有严格的断点都将被视为换行符。但是,灵活断点在每次出现时都会重新评估,并且仍然可以渲染为平面。有关更多信息,请参见 break/1flex_break/1

此实现还添加了 force_unfit/1next_break_fits/2,它们可以更好地控制文档的拟合。

总结

函数

根据给定的 string 返回一个断点文档。

折叠此节点后的任何换行符和空格,最多发出 max 个换行符。

如果 color_key 在选项中具有颜色,则对文档进行着色。

连接文档列表,返回一个新文档。

连接两个文档实体,返回一个新文档。

根据限制和内容将 collection 包裹在 leftright 中。

返回一个用于表示无的文档实体。

根据给定的 string 返回一个灵活断点文档。

粘合两个文档(doc1doc2),在它们之间插入由 break_string 给出的 flex_break/1

使用给定的文件夹函数将文档列表折叠成一个文档。

强制当前组不适合。

为给定的宽度格式化给定的文档。

粘合两个文档(doc1doc2),在它们之间插入给定的断点 break_string

返回包含指定文档 doc 的组。

强制换行符。

在两个文档之间插入强制换行符。

在给定的 level 处嵌套给定的文档。

将下一个断点视为适合。

在两个文档之间插入强制单空格。

创建由字符串表示的文档。

根据 Inspect 协议将 Elixir 项转换为代数文档。

守卫

类型

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

函数

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

根据给定的 string 返回一个断点文档。

此断点可以渲染为换行符或给定的 string,具体取决于所选布局的 mode

示例

让我们通过将两个字符串连接在一起并在它们之间插入一个断点来创建一个文档

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

请注意,断点用给定的字符串表示,因为我们没有达到行限制。一旦我们这样做,它将被替换为换行符

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", "\n", "b"]
链接到此函数

collapse_lines(max)

查看源代码 (自 1.6.0 版起)
@spec collapse_lines(pos_integer()) :: doc_collapse()

折叠此节点后的任何换行符和空格,最多发出 max 个换行符。

链接到此函数

color(doc, color_key, opts)

查看源代码 (自 1.4.0 版起)
@spec color(t(), Inspect.Opts.color_key(), Inspect.Opts.t()) :: t()

如果 color_key 在选项中具有颜色,则对文档进行着色。

@spec concat([t()]) :: t()

连接文档列表,返回一个新文档。

示例

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

连接两个文档实体,返回一个新文档。

示例

iex> doc = Inspect.Algebra.concat("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"]
链接到此函数

container_doc(left, collection, right, inspect_opts, fun, opts \\ [])

查看源代码 (自 1.6.0 版起)
@spec container_doc(
  t(),
  [any()],
  t(),
  Inspect.Opts.t(),
  (term(), Inspect.Opts.t() -> t()),
  keyword()
) ::
  t()

根据限制和内容将 collection 包裹在 leftright 中。

它使用给定的 leftright 文档作为包围,以及分隔符文档 separator 来分隔 docs 中的项目。如果集合中的所有条目都是简单文档(文本或字符串),那么此函数会尝试将尽可能多的内容放在同一行上。如果它们不是简单的,则如果它们不合适,则每行仅显示一个条目。

给定的 inspect_opts 中的限制将被遵守,当达到限制时,此函数将停止处理并输出 "..." 而不是。

选项

  • :separator - 每个文档之间使用的分隔符
  • :break - 如果为 :strict,则始终在每个元素之间换行。如果为 :flex,则仅在必要时换行。如果为 :maybe,则仅在所有元素都是基于文本时才选择 :flex,否则为 :strict

示例

iex> inspect_opts = %Inspect.Opts{limit: :infinity}
iex> fun = fn i, _opts -> to_string(i) end
iex> doc = Inspect.Algebra.container_doc("[", 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 = Inspect.Algebra.container_doc("[", 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 = Inspect.Algebra.container_doc("[", Enum.to_list(1..5), "]", inspect_opts, fun, opts)
iex> Inspect.Algebra.format(doc, 20) |> IO.iodata_to_binary()
"[1! 2! 3! ...]"
@spec empty() :: :doc_nil

返回一个用于表示无的文档实体。

示例

iex> Inspect.Algebra.empty()
:doc_nil
链接到此函数

flex_break(string \\ " ")

查看源代码 (自 1.6.0 版起)
@spec flex_break(binary()) :: doc_break()

根据给定的 string 返回一个灵活断点文档。

灵活断点仍然会导致组断裂,就像 break/1 一样,但当文档被渲染时它会被重新评估。

例如,假设一个组文档表示为 [1, 2, 3],其中每个逗号后面的空格都是一个断点。当上面的文档不适合单行时,所有断点都将被启用,导致文档渲染为

[1,
 2,
 3]

但是,如果使用灵活断点,则每个断点在渲染时都会重新评估,因此文档可以可能渲染为

[1, 2,
 3]

因此得名“灵活”。在文档拟合方面,它们更灵活。另一方面,它们更昂贵,因为每个断点都需要重新评估。

此函数由 container_doc/6 及其同类函数用于在同一行上显示最大数量的条目。

链接到此函数

flex_glue(doc1, break_string \\ " ", doc2)

查看源代码 (自 1.6.0 版起)
@spec flex_glue(t(), binary(), t()) :: t()

粘合两个文档(doc1doc2),在它们之间插入由 break_string 给出的 flex_break/1

此函数由 container_doc/6 及其同类函数用于在同一行上显示最大数量的条目。

链接到此函数

fold_doc(docs, folder_fun)

查看源代码
@spec fold_doc([t()], (t(), t() -> t())) :: t()

使用给定的文件夹函数将文档列表折叠成一个文档。

文档列表“从右边”折叠;在这方面,此函数类似于 List.foldr/3,只是它不需要初始累加器,并使用 docs 的最后一个元素作为初始累加器。

示例

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"]
链接到此函数

force_unfit(doc)

查看源代码 (自 1.6.0 版起)
@spec force_unfit(t()) :: doc_force()

强制当前组不适合。

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

为给定的宽度格式化给定的文档。

以最大宽度和要打印的文档作为参数,并返回文档在给定宽度内最佳布局的 IO 数据表示。

文档从平面(无断点)开始,直到找到一个组。

示例

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(doc1, break_string \\ " ", doc2)

查看源代码
@spec glue(t(), binary(), t()) :: t()

粘合两个文档(doc1doc2),在它们之间插入给定的断点 break_string

有关如何插入断点的更多信息,请参见 break/1

示例

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", "\t", "world"]
链接到此函数

group(doc, mode \\ :self)

查看源代码
@spec group(t(), :self | :inherit) :: doc_group()

返回包含指定文档 doc 的组。

尝试将组中的文档尽力渲染在一起。

组模式也可以设置为 :inherit,这意味着如果父组也断裂,它会自动断裂。

示例

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", "\n", "B"]
@spec line() :: t()

强制换行符。

如果组中所有行都适合,则带换行的组将适合。

示例

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

在两个文档之间插入强制换行符。

请参见 line/0

示例

iex> doc = Inspect.Algebra.line("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 80)
["Hughes", "\n", "Wadler"]
链接到此函数

nest(doc, level, mode \\ :always)

查看源代码
@spec nest(t(), non_neg_integer() | :cursor | :reset, :always | :break) ::
  doc_nest() | t()

在给定的 level 处嵌套给定的文档。

如果 level 是一个整数,则表示每次换行时追加的缩进级别。如果级别是 :cursor,则文档中“光标”的当前位置将成为嵌套级别。如果级别是 :reset,则将其重置为 0。

mode 可以是 :always,表示始终进行嵌套,或者 :break,表示仅在已断开的组内进行嵌套。

示例

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(doc, mode \\ :enabled)

查看源代码 (自 1.6.0 起)
@spec next_break_fits(t(), :enabled | :disabled) :: doc_fits()

将下一个断点视为适合。

mode 可以是 :enabled:disabled。当为 :enabled 时,它将认为文档适合,只要它找到下一个换行符,它将有效地取消换行符。它还会忽略任何在搜索下一个换行符时的 force_unfit/1

当禁用时,它将按预期行为,并且将忽略任何进一步的 next_break_fits/2 指令。

示例

Elixir 的代码格式化程序使用它来避免在某些特定位置断开代码。例如,考虑以下代码

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

现在假设这段代码不适合它的行。代码格式化程序会在 () 以及 %{} 之间引入换行符。因此,文档将断开为

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

格式化程序将表示映射的代数文档包装在 next_break_fits/1 中,以便代码格式化为

some_function_call(%{
  ...,
  key: value,
  ...
})
@spec space(t(), t()) :: t()

在两个文档之间插入强制单空格。

示例

iex> doc = Inspect.Algebra.space("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 5)
["Hughes", " ", "Wadler"]
链接到此函数

string(string)

查看源代码 (自 1.6.0 起)
@spec string(String.t()) :: doc_string()

创建由字符串表示的文档。

虽然 Inspect.Algebra 接受二进制文件作为文档,但这些文件是按二进制文件大小计算的。另一方面,string 文档是根据字形来衡量文档大小的。

示例

以下文档有 10 个字节,因此它不会在不换行的情况下格式化为宽度 9

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

但是,如果我们使用 string,则使用字符串长度而不是字节大小,可以正确地适应

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"]
@spec to_doc(any(), Inspect.Opts.t()) :: t()

根据 Inspect 协议将 Elixir 项转换为代数文档。