Макрос
Удобства для работы с макросами.
Пользовательские Сигилы
Для создания пользовательского сигила, определите функцию с именем 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(строка)
Преобразует данную строку в формат CamelCase.
- decompose_call(ast)
Разбивает локальный или удалённый вызов на его удалённую часть (если она есть), имя функции и список аргументов.
- escape(expr, opts \\ [])
Рекурсивно экранирует значение, чтобы его можно было вставить в синтаксическое дерево.
- expand(ast, env)
Принимает узел AST и расширяет его до тех пор, пока его нельзя больше расширять.
- expand_once(ast, env)
Принимает узел AST и расширяет его один раз.
- generate_arguments(количество, контекст)
Генерирует узлы AST для заданного числа требуемых переменных аргументов с помощью
Macro.var/2.- operator?(имя, арность)
Возвращает
true, если данное имя и арность являются оператором.- pipe(expr, call_args, позиция)
Направляет
exprвcall_argsв заданнойposition.- postwalk(ast, fun)
Выполняет обход дерева выражений в порядке обратного постфикса.
- postwalk(ast, acc, fun)
Выполняет обход дерева выражений в порядке обратного постфикса с помощью накопителя.
- prewalk(ast, fun)
Выполняет обход дерева выражений в прямом порядке.
- prewalk(ast, acc, fun)
Выполняет обход дерева выражений в прямом порядке с помощью накопителя.
- quoted_literal?(термин)
Возвращает
trueесли данное цитируемое выражение является литералом AST.- special_form?(имя, арность)
Возвращает
trueесли данное имя и арность являются специальной формой.- struct!(модуль, env)
Расширяет структуру, заданную
moduleв данномenv.- to_string(дерево, fun \\ fn _ast, string -> string end)
Преобразует данное выражение AST в строку.
- traverse(ast, acc, pre, post)
Выполняет обход дерева выражений в прямом порядке с помощью накопителя.
- underscore(атом)
Преобразует данный атом или двоичное представление в формат с подчеркиванием.
- unescape_string(символы)
Разворачивает данные символы.
- unescape_string(символы, карта)
Разворачивает данные символы в соответствии с заданной картой.
- unpipe(expr)
Разбивает выражение конвейера на список.
- update_meta(цитируемое, fun)
Применяет данную функцию к метаданным узла, если они содержатся.
- validate(expr)
Проверяет, являются ли данные выражения допустимыми цитируемыми выражениями.
- var(переменная, контекст)
Генерирует узел AST, представляющий переменную, заданную атомами
varиcontext.
Типы
expr()
Specs
expr() :: {expr() | atom(), keyword(), atom() | [t()]} Представляет выражения в AST
literal()
Specs
literal() :: atom() | number() | binary() | (... -> any()) | {t(), t()} | [t()] Представляет литералы в AST
t()
Specs
t() :: expr() | literal()
Абстрактное синтаксическое дерево (AST)
Функции
camelize(строка)
Характеристики
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(выражение, опции \\ [])
Характеристики
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(AST, среда)
Принимает узел AST и расширяет его до тех пор, пока это больше не возможно.
Обратите внимание, что эта функция не обходит AST, а только расширяет корневой узел.
Эта функция использует expand_once/2 в качестве внутренней функции. Проверьте её для получения более подробной информации и примеров.
expand_once(AST, среда)
Принимает узел AST и расширяет его один раз.
Следующее содержимое расширяется:
- Макросы (локальные или удалённые)
- Псевдонимы расширяются (если возможно) и возвращают атомы
- Макросы среды компиляции (
__CALLER__/0,__DIR__/0,__ENV__/0и__MODULE__/0) - Чтение атрибутов модуля (
@foo)
Если выражение не может быть расширено, возвращается само выражение. Эта функция не обходит AST, а только расширяет корневой узел.
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(количество, контекст)
Характеристики
generate_arguments(0, context :: atom()) :: []
generate_arguments(pos_integer(), context) :: [{atom(), [], context}, ...]
when context: atom() Генерирует узлы AST для заданного количества требуемых переменных аргументов, используя Macro.var/2.
Примеры
iex> Macro.generate_arguments(2, __MODULE__)
[{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}] operator?(имя, арность)
Характеристики
operator?(name :: atom(), arity()) :: boolean()
Возвращает true если заданное имя и арность являются оператором.
pipe(выражение, аргументы_вызова, позиция)
Характеристики
pipe(t(), t(), integer()) :: t()
Направляет expr в call_args в указанной position.
postwalk(AST, функция)
Характеристики
postwalk(t(), (t() -> t())) :: t()
Выполняет обход дерева выражений с цитированием в пост-порядке.
postwalk(AST, накопитель, функция)
Характеристики
postwalk(t(), any(), (t(), any() -> {t(), any()})) :: {t(), any()} Выполняет обход дерева выражений с цитированием в пост-порядке с использованием накопителя.
prewalk(AST, функция)
Характеристики
prewalk(t(), (t() -> t())) :: t()
Выполняет обход дерева выражений с цитированием в пре-порядке.
prewalk(AST, накопитель, функция)
Характеристики
prewalk(t(), any(), (t(), any() -> {t(), any()})) :: {t(), any()} Выполняет обход дерева выражений с цитированием в пре-порядке с использованием накопителя.
quoted_literal?(термин)
Характеристики
quoted_literal?(literal()) :: true
quoted_literal?(expr()) :: false
Возвращает true если данное цитируемое выражение является литералом AST.
special_form?(имя, арность)
Характеристики
special_form?(name :: atom(), arity()) :: boolean()
Возвращает true если заданное имя и арность являются специальной формой.
struct!(модуль, среда)
Характеристики
struct!(module, Macro.Env.t()) :: %module{} when module: module() Расширяет структуру, заданную module в заданной env.
Это полезно, когда структура должна быть расширена во время компиляции, а структура, которая расширяется, может быть или не быть скомпилированной. Эта функция даже способна расширять структуры, определённые в модуле, который компилируется.
Вызовет CompileError если структура недоступна.
to_string(дерево, функция \\ fn _ast, строка -> строка 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, накопитель, до, после)
Характеристики
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 будут экранироваться, если функция map возвращает true для ?x. Кодовые точки Unicode, если функция map возвращает true для ?u.
Примеры
Использование функции unescape_map/1 , определённой выше, очень просто:
Macro.unescape_string("example\\n", &unescape_map(&1)) unpipe(expr)
Спецификации
unpipe(t()) :: [t()]
Разбивает выражение конвейера на список.
AST для конвейера (последовательности применений |>) аналогичен AST для последовательности бинарных операторов или вызовов функций: выражение верхнего уровня – это правое крайнее :|> (которое выполняется последним), а его левая и правая части – это его аргументы:
quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]} В примере выше, |> конвейер – это правее конвейер; arg1 – это AST для 100 |> div(5), а arg2 – это AST для div(2).
Часто бывает полезно иметь AST такого конвейера в виде списка вызовов функций. Эта функция делает именно это:
Macro.unpipe(quote do: 100 |> div(5) |> div(2))
#=> [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}] Мы получаем список, который следует напрямую за конвейером: сначала 100, затем div(5) (точнее, его AST), затем div(2). 0 в качестве второго элемента кортежей – это позиция предыдущего элемента в конвейере внутри текущего вызова функции: {{: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.9.4/Macro.html