Макрос
Удобства работы с макросами.
Пользовательские сигилы
Для создания пользовательского сигила, определите функцию с именем sigil_{identifier}, которая принимает два аргумента. Первый аргумент — строка, второй — список символов, содержащих любые модификаторы. Если сигил в нижнем регистре (например, sigil_x), то аргумент строки позволит выполнить интерполяцию. Если сигил в верхнем регистре (например, sigil_X), то строка не будет интерполирована.
Допустимые модификаторы включают только буквы в нижнем и верхнем регистре. Другие символы приведут к синтаксической ошибке.
Модуль, содержащий пользовательский сигил, должен быть импортирован, прежде чем синтаксис сигила сможет быть использован.
Примеры
defmodule MySigils do
defmacro sigil_x(term, [?r]) do
quote do
unquote(term) |> String.reverse()
end
end
defmacro sigil_x(term, _modifiers) do
term
end
defmacro sigil_X(term, [?r]) do
quote do
unquote(term) |> String.reverse()
end
end
defmacro sigil_X(term, _modifiers) do
term
end
end
import MySigils
~x(with #{"inter" <> "polation"})
#=>"with interpolation"
~x(with #{"inter" <> "polation"})r
#=>"noitalopretni htiw"
~X(without #{"interpolation"})
#=>"without \#{"interpolation"}"
~X(without #{"interpolation"})r
#=>"}\"noitalopretni\"{# tuohtiw" Резюме
Типы
- expr()
Представляет выражения в дереве абстрактной синтаксической структуры (AST)
- literal()
Представляет литералы в дереве абстрактной синтаксической структуры (AST)
- t()
Дерево абстрактной синтаксической структуры (AST)
Функции
- camelize(string)
Преобразует данную строку в формат CamelCase.
- decompose_call(ast)
Декомпозирует локальный или удалённый вызов на его удалённую часть (если указана), имя функции и список аргументов.
- escape(expr, opts \\ [])
Рекурсивно экранирует значение, чтобы его можно было вставить в дерево синтаксического анализа.
- expand(tree, env)
Принимает узел AST и расширяет его до тех пор, пока он больше не может быть расширен.
- expand_once(ast, env)
Принимает узел AST и расширяет его один раз.
- generate_arguments(amount, context)
Генерирует узлы AST для заданного количества необходимых переменных аргументов, используя
Macro.var/2.- operator?(name, arity)
Возвращает
trueесли данное имя и арность являются оператором.- pipe(expr, call_args, position)
Направляет
exprвcall_argsв заданнойposition.- postwalk(ast, fun)
Выполняет обход дерева выражений, пост-обход, по глубине.
- postwalk(ast, acc, fun)
Выполняет обход дерева выражений, пост-обход, по глубине, используя накопитель.
- prewalk(ast, fun)
Выполняет обход дерева выражений, пре-обход, по глубине.
- prewalk(ast, acc, fun)
Выполняет обход дерева выражений, пре-обход, по глубине, используя накопитель.
- quoted_literal?(term)
Возвращает
trueесли данное цитируемое выражение является литералом AST.- special_form?(name, arity)
Возвращает
trueесли данное имя и арность являются специальной формой.- struct!(module, env)
Расширяет структуру, заданную
module, в заданномenv.- to_string(tree, fun \\ fn _ast, string -> string end)
Преобразует данное выражение AST в строку.
- traverse(ast, acc, pre, post)
Выполняет обход дерева выражений по глубине, используя накопитель.
- underscore(atom)
Преобразует данный атом или бинарное представление в формат с нижним подчеркиванием.
- unescape_string(chars)
Раскладывает данные символы.
- unescape_string(chars, map)
Раскладывает данные символы в соответствии с заданной картой.
- unpipe(expr)
Разбивает выражение конвейера на список.
- update_meta(quoted, fun)
Применяет данную функцию к метаданным узла, если он содержит их.
- validate(expr)
Проверяет, что данные выражения являются валидными цитируемыми выражениями.
- var(var, context)
Генерирует узел AST, представляющий переменную, заданную атомами
varиcontext.
Типы
expr()
expr() :: {expr() | atom(), keyword(), atom() | [t()]} Представляет выражения в дереве AST.
literal()
literal() ::
atom() | number() | binary() | (... -> any()) | {t(), t()} | [t()] Представляет литералы в дереве AST.
t()
t() :: expr() | literal()
Дерево абстрактной синтаксической структуры (AST).
Функции
camelize(string)
camelize(String.t()) :: String.t()
Преобразует данную строку в формат CamelCase.
Эта функция была разработана для преобразования идентификаторов/токенов языка в CamelCase, поэтому она принадлежит модулю Macro. Не используйте её как общий механизм для преобразования строк в CamelCase, так как она не поддерживает Unicode или символы, которые не являются допустимыми в идентификаторах Elixir.
Примеры
iex> Macro.camelize("foo_bar")
"FooBar" Если присутствуют символы в верхнем регистре, они никак не изменяются как механизм сохранения аббревиатур:
iex> Macro.camelize("API.V1")
"API.V1"
iex> Macro.camelize("API_SPEC")
"API_SPEC" decompose_call(ast)
decompose_call(t()) ::
{atom(), [t()]} | {t(), atom(), [t()]} | :error Декомпозирует локальный или удалённый вызов на его удалённую часть (если указана), имя функции и список аргументов.
Возвращает :error при предоставлении некорректного синтаксиса вызова.
Примеры
iex> Macro.decompose_call(quote(do: foo))
{:foo, []}
iex> Macro.decompose_call(quote(do: foo()))
{:foo, []}
iex> Macro.decompose_call(quote(do: foo(1, 2, 3)))
{:foo, [1, 2, 3]}
iex> Macro.decompose_call(quote(do: Elixir.M.foo(1, 2, 3)))
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}
iex> Macro.decompose_call(quote(do: 42))
:error escape(expr, opts \\ [])
escape(term(), keyword()) :: t()
Рекурсивно экранирует значение, чтобы его можно было вставить в дерево синтаксического анализа.
Примеры
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1 Параметры
-
:unquote- если true, эта функция оставляет выраженияunquote/1иunquote_splicing/1не экранированными, фактически раскладывая содержимое при экранировании. Этот параметр полезен только при экранировании AST, которые могут содержать цитируемые фрагменты. По умолчанию false. -
:prune_metadata- если true, удаляет метаданные из экранированных узлов AST. Обратите внимание, что этот параметр меняет семантику экранированного кода и его следует использовать только при экранировании AST, а не значений. По умолчанию false.Например,
ExUnitхранит AST каждого утверждения, поэтому при сбое утверждения мы можем показать фрагменты кода пользователям. Без этого параметра каждый раз при компиляции модуля мы получаем разный MD5 модуля байткода, потому что AST содержит метаданные, такие как счётчики, специфичные для среды компиляции. Удаляя метаданные, мы гарантируем детерминированность модуля и уменьшаем объём данных, которыеExUnitдолжен хранить.
Сравнение с Kernel.SpecialForms.quote/2
Функция escape/2 иногда путают с Kernel.SpecialForms.quote/2, потому что вышеприведённые примеры ведут себя одинаково с обеими. Ключевое различие лучше всего иллюстрируется, когда значение для экранирования хранится в переменной.
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> quote do: {:a, :b, :c}
{:{}, [], [:a, :b, :c]}
iex> value = {:a, :b, :c}
iex> Macro.escape(value)
{:{}, [], [:a, :b, :c]}
iex> quote do: value
{:value, [], __MODULE__}
iex> value = {:a, :b, :c}
iex> quote do: unquote(value)
{:a, :b, :c} escape/2 используется для экранирования значений (либо непосредственно переданных, либо связанных с переменной), в то время как Kernel.SpecialForms.quote/2 создаёт деревья синтаксического анализа для выражений.
expand(tree, env)
Принимает узел AST и расширяет его до тех пор, пока он больше не может быть расширен.
Эта функция использует expand_once/2 в своём внутреннем коде. Подробнее ознакомьтесь с ней для получения дополнительной информации и примеров.
expand_once(ast, env)
Принимает узел AST и расширяет его один раз.
Ниже перечислены расширяемые элементы:
- Макросы (локальные или удалённые)
- Псевдонимы расширяются (если возможно) и возвращают атомы
- Макросы среды компиляции (
__CALLER__/0,__DIR__/0,__ENV__/0и__MODULE__/0) - Чтение атрибутов модуля (
@foo)
Если выражение не может быть расширено, оно возвращает само выражение. Обратите внимание, что expand_once/2 выполняет расширение только один раз, и оно не рекурсивное. См. expand/2 для расширения до тех пор, пока узел больше не может быть расширен.
Примеры
В примере ниже у нас есть макрос, который генерирует модуль с функцией с именем name_length, которая возвращает длину имени модуля. Значение этой функции будет вычислено во время компиляции, а не во время выполнения.
Рассмотрим реализацию ниже:
defmacro defmodule_with_length(name, do: block) do
length = length(Atom.to_charlist(name))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end При вызове так:
defmodule_with_length My.Module do def other_function, do: ... end
Компиляция завершится ошибкой, потому что My.Module в кавычках не является атомом, а синтаксическим деревом, как показано ниже:
{:__aliases__, [], [:My, :Module]} Это означает, что нам нужно расширить узел псевдонимов выше до атома, чтобы мы могли получить его длину. Расширение узла не является простым, потому что нам также необходимо расширить псевдонимы вызывающей стороны. Например:
alias MyHelpers, as: My defmodule_with_length My.Module do def other_function, do: ... end
Конечное имя модуля будет MyHelpers.Module, а не My.Module. С помощью Macro.expand/2 учитываются такие псевдонимы. Также расширяются локальные и удаленные макросы. Мы можем переписать наш макрос выше, используя эту функцию как:
defmacro defmodule_with_length(name, do: block) do
expanded = Macro.expand(name, __CALLER__)
length = length(Atom.to_charlist(expanded))
quote do
defmodule unquote(name) do
def name_length, do: unquote(length)
unquote(block)
end
end
end generate_arguments(amount, context)
(с версии 1.5.0)Генерирует узлы AST для заданного числа требуемых переменных аргументов, используя Macro.var/2.
Примеры
iex> Macro.generate_arguments(2, __MODULE__)
[{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}] operator?(name, arity)
(с версии 1.7.0)operator?(name :: atom(), arity()) :: boolean()
Возвращает true если заданное имя и арность являются оператором.
pipe(expr, call_args, position)
pipe(t(), t(), integer()) :: t()
Подключает expr к call_args в заданной position.
postwalk(ast, fun)
postwalk(t(), (t() -> t())) :: t()
Выполняет обход древовидной структуры выражений в порядке следования снизу вверх.
postwalk(ast, acc, fun)
postwalk(t(), any(), (t(), any() -> {t(), any()})) ::
{t(), any()} Выполняет обход древовидной структуры выражений в порядке следования снизу вверх с использованием аккумулятора.
prewalk(ast, fun)
prewalk(t(), (t() -> t())) :: t()
Выполняет обход древовидной структуры выражений в порядке следования сверху вниз.
prewalk(ast, acc, fun)
prewalk(t(), any(), (t(), any() -> {t(), any()})) ::
{t(), any()} Выполняет обход древовидной структуры выражений в порядке следования сверху вниз с использованием аккумулятора.
quoted_literal?(term)
(с версии 1.7.0)quoted_literal?(literal()) :: true
quoted_literal?(expr()) :: false
Возвращает true если заданное выражение в кавычках является литералом AST.
special_form?(name, arity)
(с версии 1.7.0)special_form?(name :: atom(), arity()) :: boolean()
Возвращает true если заданное имя и арность являются специальной формой.
struct!(module, env)
(с версии 1.8.0)struct!(module, Macro.Env.t()) :: %module{} when module: module() Расширяет структуру, заданную module в заданном env.
Это полезно, когда структура должна быть расширена во время компиляции, и структура, которая расширяется, может или не может быть скомпилирована. Эта функция даже способна расширять структуры, определенные в модуле, который компилируется.
Она вызовет CompileError, если структура недоступна.
to_string(tree, fun \\ fn _ast, string -> string end)
to_string(t(), (t(), String.t() -> String.t())) :: String.t()
Преобразует заданное выражение AST в строку.
Переданная fun вызывается для каждого узла AST с двумя аргументами: AST узла, который печатается, и строковое представление этого же узла. Возвращаемое значение этой функции используется в качестве конечного строкового представления этого узла AST.
Эта функция отбрасывает все форматирование исходного кода.
Примеры
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3))) "foo.bar(1, 2, 3)" iex> Macro.to_string(quote(do: 1 + 2), fn ...> 1, _string -> "one" ...> 2, _string -> "two" ...> _ast, string -> string ...> end) "one + two"
traverse(ast, acc, pre, post)
traverse(
t(),
any(),
(t(), any() -> {t(), any()}),
(t(), any() -> {t(), any()})
) :: {t(), any()} Выполняет обход древовидной структуры выражений в порядке следования сверху вниз с использованием аккумулятора.
underscore(atom)
underscore(atom() | String.t()) :: String.t()
Преобразует заданный атом или двоичное значение в формат с нижним подчеркиванием.
Если задан атом, предполагается, что это модуль Elixir, поэтому он преобразуется в двоичное значение, а затем обрабатывается.
Эта функция была разработана для преобразования идентификаторов/токенов языка в нижний регистр с подчеркиванием, поэтому она принадлежит модулю Macro. Не используйте её как общий механизм для преобразования строк в нижний регистр с подчеркиванием, так как она не поддерживает Unicode или символы, которые не являются допустимыми в идентификаторах Elixir.
Примеры
iex> Macro.underscore("FooBar")
"foo_bar"
iex> Macro.underscore("Foo.Bar")
"foo/bar"
iex> Macro.underscore(Foo.Bar)
"foo/bar" В общем случае, underscore можно рассматривать как обратное преобразование camelize, однако в некоторых случаях форматирование может быть утеряно:
iex> Macro.underscore("SAPExample")
"sap_example"
iex> Macro.camelize("sap_example")
"SapExample"
iex> Macro.camelize("hello_10")
"Hello10" unescape_string(chars)
unescape_string(String.t()) :: String.t()
Распаковывает заданные символы.
Это поведение распаковки, используемое по умолчанию в Elixir в одинарных и двойных кавычках. Смотрите unescape_string/2 за информацией о том, как настроить карту экранирования.
В этой настройке Elixir экранирует следующее: \0, \a, \b, \d, \e, \f, \n, \r, \s, \t и \v. Байты могут быть заданы в шестнадцатеричном формате через \xNN, а коды Unicode — через \uNNNN экранирование.
Эта функция обычно используется в реализациях сигил (например, ~r, ~s и других), которые получают необработанную, неэкранированную строку.
Примеры
iex> Macro.unescape_string("example\\n")
"example\n" В примере выше мы передаём строку с \n экранированной и возвращаем версию с ней неэкранированной.
unescape_string(chars, map)
unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) :: String.t()
Распаковывает заданные символы согласно заданной карте.
Смотрите unescape_string/1, если вы хотите использовать ту же карту, что и Elixir в одинарных и двойных кавычках.
Карта
Карта должна быть функцией. Функция получает целое число, представляющее код символа, который она хочет распаковать. Вот функция отображения по умолчанию, реализованная в Elixir:
def unescape_map(unicode), do: true def unescape_map(hex), do: true def unescape_map(?0), do: ?0 def unescape_map(?a), do: ?\a def unescape_map(?b), do: ?\b def unescape_map(?d), do: ?\d def unescape_map(?e), do: ?\e def unescape_map(?f), do: ?\f def unescape_map(?n), do: ?\n def unescape_map(?r), do: ?\r def unescape_map(?s), do: ?\s def unescape_map(?t), do: ?\t def unescape_map(?v), do: ?\v def unescape_map(e), do: e
Если функция unescape_map/1 возвращает false, символ не экранирован, и обратная косая черта сохраняется в строке.
Шестнадцатеричные и коды Unicode будут экранированы, если функция отображения возвращает true для ?x. Коды Unicode, если функция отображения возвращает true для ?u.
Примеры
Использование функции unescape_map/1 определенной выше просто:
Macro.unescape_string("example\\n", &unescape_map(&1)) unpipe(expr)
unpipe(t()) :: [t()]
Разбивает выражение pipeline в список.
AST для pipeline (последовательности применений |>) похож на AST последовательности бинарных операторов или применений функций: выражение верхнего уровня — это правое :|> (которое выполняется последним), а его левая и правая части — это его аргументы:
quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]} В примере выше, |> pipe — это правое pipe; arg1 — AST для 100 |> div(5), а arg2 — AST для div(2).
Часто полезно иметь AST для такого pipeline в виде списка применений функций. Эта функция делает именно это:
Macro.unpipe(quote do: 100 |> div(5) |> div(2))
#=> [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] Мы получаем список, который следует непосредственно за pipeline: сначала 100, затем div(5) (точнее, его AST), затем div(2). 0 как второй элемент кортежей — это позиция предыдущего элемента в pipeline внутри текущего применения функции: {{:div, [], [5]}, 0} означает, что предыдущий элемент (100) будет вставлен в качестве 0-го (первого) аргумента функции div/2, так что AST для этой функции станет {:div, [], [100, 5]} (div(100, 5)).
update_meta(quoted, fun)
update_meta(t(), (keyword() -> keyword())) :: t()
Применяет заданную функцию к метаданным узла, если он содержит их.
Это часто бывает полезно при использовании с Macro.prewalk/2 для удаления информации, такой как строки и счётчики гигиены, из выражения для хранения или сравнения.
Примеры
iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []} validate(expr)
validate(term()) :: :ok | {:error, term()} Проверяет, являются ли заданные выражения допустимыми выражениями в кавычках.
Проверяет Macro.t/0 для спецификации допустимого выражения в кавычках.
Возвращает :ok если выражение является допустимым. В противном случае возвращает кортеж в форме {:error, remainder}, где remainder — недействительная часть выражения в кавычках.
Примеры
iex> Macro.validate({:two_element, :tuple})
:ok
iex> Macro.validate({:three, :element, :tuple})
{:error, {:three, :element, :tuple}}
iex> Macro.validate([1, 2, 3])
:ok
iex> Macro.validate([1, 2, 3, {4}])
{:error, {4}} var(var, context)
var(var, context) :: {var, [], context} when var: atom(), context: atom() Генерирует узел AST, представляющий переменную, заданную атомами var и context.
Примеры
Для построения переменной требуется контекст. Большинство раз для сохранения гигиены контекст должен быть __MODULE__/0:
iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__} Однако, если необходимо получить доступ к переменной пользователя, можно задать nil:
iex> Macro.var(:foo, nil)
{:foo, [], nil}
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.8.2/Macro.html