查看源代码 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/2
、space/2
和 line/2
可以帮助您将文档整理成一个严格的结构。但是,代数文档在使用像 glue/3
和 group/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/1
和 flex_break/1
。
此实现还添加了 force_unfit/1
和 next_break_fits/2
,它们可以更好地控制文档的拟合。
总结
函数
根据给定的 string
返回一个断点文档。
折叠此节点后的任何换行符和空格,最多发出 max
个换行符。
如果 color_key
在选项中具有颜色,则对文档进行着色。
连接文档列表,返回一个新文档。
连接两个文档实体,返回一个新文档。
根据限制和内容将 collection
包裹在 left
和 right
中。
返回一个用于表示无的文档实体。
根据给定的 string
返回一个灵活断点文档。
粘合两个文档(doc1
和 doc2
),在它们之间插入由 break_string
给出的 flex_break/1
。
使用给定的文件夹函数将文档列表折叠成一个文档。
强制当前组不适合。
为给定的宽度格式化给定的文档。
粘合两个文档(doc1
和 doc2
),在它们之间插入给定的断点 break_string
。
返回包含指定文档 doc
的组。
强制换行符。
在两个文档之间插入强制换行符。
在给定的 level
处嵌套给定的文档。
将下一个断点视为适合。
在两个文档之间插入强制单空格。
创建由字符串表示的文档。
根据 Inspect
协议将 Elixir 项转换为代数文档。
守卫
类型
函数
@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"]
@spec collapse_lines(pos_integer()) :: doc_collapse()
折叠此节点后的任何换行符和空格,最多发出 max
个换行符。
@spec color(t(), Inspect.Opts.color_key(), Inspect.Opts.t()) :: t()
如果 color_key
在选项中具有颜色,则对文档进行着色。
连接文档列表,返回一个新文档。
示例
iex> doc = Inspect.Algebra.concat(["a", "b", "c"])
iex> Inspect.Algebra.format(doc, 80)
["a", "b", "c"]
连接两个文档实体,返回一个新文档。
示例
iex> doc = Inspect.Algebra.concat("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"]
@spec container_doc( t(), [any()], t(), Inspect.Opts.t(), (term(), Inspect.Opts.t() -> t()), keyword() ) :: t()
根据限制和内容将 collection
包裹在 left
和 right
中。
它使用给定的 left
和 right
文档作为包围,以及分隔符文档 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
@spec flex_break(binary()) :: doc_break()
根据给定的 string
返回一个灵活断点文档。
灵活断点仍然会导致组断裂,就像 break/1
一样,但当文档被渲染时它会被重新评估。
例如,假设一个组文档表示为 [1, 2, 3]
,其中每个逗号后面的空格都是一个断点。当上面的文档不适合单行时,所有断点都将被启用,导致文档渲染为
[1,
2,
3]
但是,如果使用灵活断点,则每个断点在渲染时都会重新评估,因此文档可以可能渲染为
[1, 2,
3]
因此得名“灵活”。在文档拟合方面,它们更灵活。另一方面,它们更昂贵,因为每个断点都需要重新评估。
此函数由 container_doc/6
及其同类函数用于在同一行上显示最大数量的条目。
粘合两个文档(doc1
和 doc2
),在它们之间插入由 break_string
给出的 flex_break/1
。
此函数由 container_doc/6
及其同类函数用于在同一行上显示最大数量的条目。
使用给定的文件夹函数将文档列表折叠成一个文档。
文档列表“从右边”折叠;在这方面,此函数类似于 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"]
@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"
粘合两个文档(doc1
和 doc2
),在它们之间插入给定的断点 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"]
@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"]
在两个文档之间插入强制换行符。
请参见 line/0
。
示例
iex> doc = Inspect.Algebra.line("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 80)
["Hughes", "\n", "Wadler"]
@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"]
@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,
...
})
在两个文档之间插入强制单空格。
示例
iex> doc = Inspect.Algebra.space("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 5)
["Hughes", " ", "Wadler"]
@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 项转换为代数文档。