Макросы
Удобства для работы с макросами.
Пользовательские символы
Для создания пользовательского символа, определите функцию с именем 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()
- t()
-
Дерево абстрактного синтаксиса (AST)
Функции
- camelize(string)
-
Преобразует заданную строку в формат CamelCase
- decompose_call(ast)
-
Разбивает локальный или удалённый вызов на его удалённую часть (если она предоставлена), имя функции и список аргументов
- escape(expr, opts \\ [])
-
Рекурсивно эскэпирует значение, чтобы его можно было вставить в дерево синтаксиса
- expand(tree, env)
-
Принимает узел AST и расширяет его до тех пор, пока это больше не возможно
- expand_once(ast, env)
-
Принимает узел AST и расширяет его один раз
- pipe(expr, call_args, position)
-
Направляет
exprвcall_argsв заданнойposition - postwalk(ast, fun)
-
Выполняет обход дерева ссылок в постфиксной форме (обход в глубину)
- postwalk(ast, acc, fun)
-
Выполняет обход дерева ссылок в постфиксной форме (обход в глубину) с использованием аккумулятора
- prewalk(ast, fun)
-
Выполняет обход дерева ссылок в префиксной форме (обход в глубину)
- prewalk(ast, acc, fun)
-
Выполняет обход дерева ссылок в префиксной форме (обход в глубину) с использованием аккумулятора
- to_string(tree, fun \\ fn _ast, string -> string end)
-
Преобразует данное выражение в двоичное представление
- traverse(ast, acc, pre, post)
-
Выполняет обход дерева ссылок в глубину с использованием аккумулятора
- underscore(atom)
-
Преобразует данный атом или двоичное представление в формат с нижним подчеркиванием
- unescape_string(chars)
-
Раскрывает эскэпированные символы из строки
- unescape_string(chars, map)
-
Раскрывает эскэпированные символы из строки согласно заданной карте
- unescape_tokens(tokens)
-
Раскрывает эскэпированные символы из токенов согласно стандартной карте
- unescape_tokens(tokens, map)
-
Раскрывает эскэпированные символы из токенов согласно заданной карте
- unpipe(expr)
-
Разбивает выражение конвейера на список
- update_meta(quoted, fun)
-
Применяет данную функцию к метаданным узла, если они содержатся
- validate(expr)
-
Проверяет, являются ли данные выражения допустимыми выражениями ссылок
- var(var, context)
-
Создаёт узел AST, представляющий переменную, заданную атомами
varиcontext
Типы
expr()
expr() :: {expr() | atom(), Keyword.t(), atom() | [t()]} t()
t() ::
expr() |
{t(), t()} |
atom() |
number() |
binary() |
pid() |
(... -> any()) |
[t()] Дерево абстрактного синтаксиса (AST)
Функции
camelize(string)
camelize(String.t()) :: String.t()
Преобразует заданную строку в формат CamelCase.
Эта функция была разработана для преобразования идентификаторов/токенов языка в CamelCase, поэтому она принадлежит модулю Macro. Не используйте её в качестве общего механизма для преобразования строк в CamelCase, так как она не поддерживает Unicode или символы, которые не являются допустимыми в идентификаторах Elixir.
Примеры
iex> Macro.camelize "foo_bar" "FooBar"
decompose_call(ast)
decompose_call(Macro.t()) ::
{atom(), [Macro.t()]} |
{Macro.t(), atom(), [Macro.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()) :: Macro.t()
Рекурсивно эскэпирует значение, чтобы его можно было вставить в дерево синтаксиса.
Можно передать unquote: true в escape/2, что оставляет выражения unquote/1 не эскэпированными, эффективно удаляя кавычки из содержимого при эскэпировании.
Примеры
iex> Macro.escape(:foo)
:foo
iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1 expand(tree, env)
Принимает узел AST и расширяет его до тех пор, пока это больше не возможно.
Эта функция использует expand_once/2 внутри. Смотрите её для получения дополнительной информации и примеров.
expand_once(ast, env)
Принимает узел AST и расширяет его один раз.
Следующие элементы расширяются:
- Макросы (локальные или удалённые)
- Псевдонимы расширяются (если возможно) и возвращают атомы
- Макросы среды компиляции (
__ENV__/0,__MODULE__/0и__DIR__/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 pipe(expr, call_args, position)
pipe(Macro.t(), Macro.t(), integer()) :: Macro.t() | no_return()
Направляет 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()} Выполняет обход дерева ссылок в префиксной форме (обход в глубину) с использованием аккумулятора.
to_string(tree, fun \\ fn _ast, string -> string end)
to_string(Macro.t(), (Macro.t(), String.t() -> String.t())) :: String.t()
Преобразует данное выражение в двоичное представление.
Переданная 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)
Преобразует данный атом или двоичное представление в формат с нижним подчеркиванием.
Если атом задан, предполагается, что это модуль 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(?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(?x), do: true def unescape_map(?u), do: true 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)
unescape_tokens(tokens)
unescape_tokens([Macro.t()]) :: [Macro.t()]
Разворачивает заданные токены в соответствии с картой по умолчанию.
Проверьте unescape_string/1 и unescape_string/2 для получения дополнительной информации о разворачивании.
Только токены, которые являются двоичными, разворачиваются, все остальные игнорируются. Эта функция полезна при реализации собственных сигилов. Проверьте реализацию Kernel.sigil_s/2 для примеров.
unescape_tokens(tokens, map)
unescape_tokens([Macro.t()], (non_neg_integer() -> non_neg_integer() | false)) :: [Macro.t()]
Разворачивает заданные токены в соответствии с заданной картой.
Проверьте unescape_tokens/1 и unescape_string/2 для получения дополнительной информации.
unpipe(expr)
unpipe(Macro.t()) :: [Macro.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.t() -> Keyword.t())) :: 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.4.5/Macro.html