Inspect.Algebra
Набор функций для создания и обработки алгебраических документов.
Этот модуль реализует функциональность, описанную в «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 основана на статье «Strictly Pretty» Линдига, которая использует предыдущие алгоритмы красивой печати, но адаптирована для строгих языков, таких как Elixir. Основная идея статьи — использование явных групповых документов, которые отображаются как плоские (разрывы как пробелы) или как разрывы (разрывы как новые строки).
Эта реализация предоставляет два типа разрывов: :strict и :flex. Когда группа не помещается, все строгие разрывы обрабатываются как новые строки. Гибкие разрывы, однако, переоцениваются при каждом появлении и могут по-прежнему отображаться как плоские. Для получения дополнительной информации см. break/1 и flex_break/1.
Эта реализация также добавляет force_unfit/1 и next_break_fits/2, которые предоставляют больший контроль над подгонкой документа.
Резюме
Типы
Защитные условия
Функции
- break(string \\ " ")
Возвращает документ разрыва, основанный на заданном
string.- collapse_lines(max)
Сжимает любые новые строки и пробелы, следующие за этим узлом, вызывая до
maxновых строк.- color(doc, color_key, opts)
Окрашивает документ, если
color_keyимеет цвет в параметрах.- concat(docs)
Конкатенирует список документов, возвращая новый документ.
- concat(doc1, doc2)
Конкатенирует два документа, возвращая новый документ.
- container_doc(left, collection, right, inspect_opts, fun, opts \\ [])
Оборачивает
collectionвleftиrightв соответствии с лимитом и содержимым.- empty()
Возвращает сущность документа, используемую для представления пустоты.
- flex_break(string \\ " ")
Возвращает документ гибкого разрыва, основанный на заданном
string.- flex_glue(doc1, break_string \\ " ", doc2)
Склеивает два документа (
doc1иdoc2) вставляяflex_break/1, заданныйbreak_stringмежду ними.- fold_doc(docs, folder_fun)
Складывает список документов в документ, используя заданную функцию.
- force_unfit(doc)
Принудительно делает текущую группу непригодной.
- format(doc, width)
Форматирует заданный документ для заданной ширины.
- glue(doc1, break_string \\ " ", doc2)
Склеивает два документа (
doc1иdoc2) вставляя заданный разрывbreak_stringмежду ними.- group(doc, mode \\ :self)
Возвращает группу, содержащую указанный документ
doc.- line()
Обязательный разрыв строки.
- line(doc1, doc2)
Вставляет обязательный разрыв строки между двумя документами.
- nest(doc, level, mode \\ :always)
Вкладывает заданный документ на заданный
level.- next_break_fits(doc, mode \\ :enabled)
Рассматривает следующий разрыв как подходящий.
- space(doc1, doc2)
Вставляет обязательный одиночный пробел между двумя документами.
- string(string)
Создаёт документ, представленный строкой.
- to_doc(term, opts)
Преобразует термин Elixir в алгебраический документ в соответствии с протоколом
Inspect.
Типы
t()Источник
@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()
Защитные условия
is_doc(doc)Источник
Функции
break(строка \\ " ")Source
@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)Source
@spec collapse_lines(pos_integer()) :: doc_collapse()
Сжимает все новые строки и пробелы, следующие за этим узлом, выводимые до max новых строк.
color(документ, цвет_ключ, опции)Source
@spec color(t(), Inspect.Opts.color_key(), Inspect.Opts.t()) :: t()
Окрашивает документ, если color_key имеет цвет в опциях.
concat(документы)Source
@spec concat([t()]) :: t()
Конкатенирует список документов, возвращая новый документ.
Примеры
iex> doc = Inspect.Algebra.concat(["a", "b", "c"]) iex> Inspect.Algebra.format(doc, 80) ["a", "b", "c"]
concat(документ1, документ2)Source
@spec concat(t(), t()) :: t()
Конкатенирует два сущности документа, возвращая новый документ.
Примеры
iex> doc = Inspect.Algebra.concat("hello", "world")
iex> Inspect.Algebra.format(doc, 80)
["hello", "world"] container_doc(слева, коллекция, справа, опции_инспекции, функция, опции \\ [])Source
@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! ...]" empty()Source
@spec empty() :: :doc_nil
Возвращает сущность документа, используемую для представления пустоты.
Примеры
iex> Inspect.Algebra.empty() :doc_nil
flex_break(строка \\ " ")Source
@spec flex_break(binary()) :: doc_break()
Возвращает документ гибкого разрыва, основанный на заданной string.
Гибкий разрыв всё ещё вызывает разрыв группы, как break/1, но он переоценивается при рендеринге документа.
Например, рассмотрим документ группы, представленный как [1, 2, 3], где пробел после каждой запятой является разрывом. Когда документ выше не помещается в одну строку, все разрывы активируются, что приводит к отображению документа как:
[1, 2, 3]
Однако, если используются гибкие разрывы, каждый разрыв переоценивается при рендеринге, поэтому документ может быть отображён как:
[1, 2, 3]
Отсюда и название «гибкий». Они более гибкие, когда речь идёт о размещении документа. С другой стороны, они дороже, так как каждый разрыв нужно переоценивать.
Эта функция используется container_doc/6 и другими, чтобы определить максимальное количество элементов в одной строке.
flex_glue(документ1, разделитель \\ " ", документ2)Source
@spec flex_glue(t(), binary(), t()) :: t()
Соединяет два документа (doc1 и doc2) вставляя flex_break/1, заданный break_string между ними.
Эта функция используется container_doc/6 и другими, чтобы определить максимальное количество элементов в одной строке.
fold_doc(документы, функция_свертки)Source
@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(документ)Source
@spec force_unfit(t()) :: doc_force()
Принудительно делает текущую группу непригодной.
format(документ, ширина)Source
@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(документ1, разделитель \\ " ", документ2)Source
@spec glue(t(), binary(), t()) :: t()
Соединяет два документа (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"] group(документ, режим \\ :self)Source
@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"]
line()Source
@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(документ1, документ2)Source
@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)Source
@spec nest(t(), non_neg_integer() | :cursor | :reset, :always | :break) :: doc_nest()
Вкладывает данный документ в указанный 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)Source
@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,
...
}) space(doc1, doc2)Source
@spec space(t(), t()) :: t()
Вставляет обязательный одиночный пробел между двумя документами.
Примеры
iex> doc = Inspect.Algebra.space("Hughes", "Wadler")
iex> Inspect.Algebra.format(doc, 5)
["Hughes", " ", "Wadler"] string(string)Source
@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"] to_doc(term, opts)Source
@spec to_doc(any(), Inspect.Opts.t()) :: t()
Преобразует термин Elixir в документ алгебры в соответствии с протоколом Inspect.
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.13.4/Inspect.Algebra.html