Macro
Функции для манипулирования AST и реализации макросов.
Макросы — это конструкции времени компиляции, которые принимают AST Elixir в качестве входных данных и возвращают AST Elixir в качестве выходных данных.
Многие функции в этом модуле существуют именно для работы с AST Elixir, для обхода, запроса и преобразования его.
Давайте рассмотрим простой пример, который демонстрирует разницу между функциями и макросами:
defmodule Example do
defmacro macro_inspect(value) do
IO.inspect(value)
value
end
def fun_inspect(value) do
IO.inspect(value)
value
end
end
Теперь давайте попробуем:
import Example macro_inspect(1) #=> 1 #=> 1 fun_inspect(1) #=> 1 #=> 1
Пока они ведут себя одинаково, так как мы передаём целое число в качестве аргумента. Но давайте посмотрим, что произойдёт, если мы передадим выражение:
macro_inspect(1 + 2)
#=> {:+, [line: 3], [1, 2]}
#=> 3
fun_inspect(1 + 2)
#=> 3
#=> 3
Макрос получает представление кода, заданного в качестве аргумента, в то время как функция получает результат кода, заданного в качестве аргумента. Макрос должен возвращать супермножество представления кода. Дополнительную информацию см. в input/0 и output/0.
Чтобы узнать больше об AST Elixir и о том, как их создавать программно, см. quote/2.
Примечание: функции в этом модуле не вычисляют код. Фактически, вычисление кода из макросов часто является антипаттерном. Для вычисления кода см. модуль
Code.
Настраиваемые сигилы
Макросы также часто используются для реализации настраиваемых сигилов. Чтобы создать настраиваемый сигил, определите макрос с именем 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" Краткое описание
Типы
- captured_remote_function()
Захваченная удалённая функция в формате &Mod.fun/arity
- input()
Входы макроса
- metadata()
Список ключевых слов метаданных AST.
- output()
Выход макроса
- t()
Абстрактное синтаксическое дерево (AST)
Функции
- camelize(string)
Преобразует заданную строку в формат CamelCase.
- classify_atom(atom)
Классифицирует runtime
atomна основе возможного размещения в AST.- dbg(code, options, env)
По умолчанию бэкэнд для
Kernel.dbg/2.- decompose_call(ast)
Декомпозирует локальный или удалённый вызов на его удалённую часть (если указана), имя функции и список аргументов.
- escape(expr, opts \\ [])
Рекурсивно экранирует значение, чтобы его можно было вставить в синтаксическое дерево.
- expand(ast, env)
Принимает узел AST и расширяет его до тех пор, пока больше не может быть расширен.
- expand_literals(ast, env)
Расширяет все литералы в
astс заданнымenv.- expand_literals(ast, acc, fun)
Расширяет все литералы в
astс заданнымaccиfun.- expand_once(ast, env)
Принимает узел AST и расширяет его один раз.
- generate_arguments(amount, context)
Генерирует узлы AST для заданного количества требуемых переменных аргументов, используя
Macro.var/2.- generate_unique_arguments(amount, context)
Генерирует узлы AST для заданного количества требуемых переменных аргументов, используя
Macro.unique_var/2.- inspect_atom(source_format, atom)
Проверяет
atomв соответствии с различными форматами источников.- operator?(name, arity)
Возвращает
trueесли заданное имя и арность являются оператором.- path(ast, fun)
Возвращает путь к узлу в
ast, для которогоfunвозвращает true.- pipe(expr, call_args, position)
Направляет
exprвcall_argsв заданнойposition.- postwalk(ast, fun)
Эта функция работает так же, как
prewalk/2, но выполняет обход по цитируемым выражениям в пост-порядке, при глубине.- postwalk(ast, acc, fun)
Эта функция работает так же, как
prewalk/3, но выполняет обход по цитируемым выражениям в пост-порядке, при глубине, используя накопитель.- postwalker(ast)
Возвращает перечисляемый объект, который обходит
astв пост-порядке, при глубине.- prewalk(ast, fun)
Выполняет обход по цитируемым выражениям в пре-порядке, при глубине.
- prewalk(ast, acc, fun)
Выполняет обход по цитируемым выражениям в пре-порядке, при глубине, используя накопитель.
- prewalker(ast)
Возвращает перечисляемый объект, который обходит
astв пре-порядке, при глубине.- quoted_literal?(term)
Возвращает
trueесли данное цитируемое выражение представляет цитируемый литерал.- special_form?(name, arity)
Возвращает
trueесли заданное имя и арность являются специальной формой.- struct!(module, env)
Расширяет структуру, заданную
moduleв данномenv.- to_string(tree)
Преобразует данное выражение AST в строку.
- to_string(tree, fun) устаревшее
Преобразует данное выражение AST в строку.
- traverse(ast, acc, pre, post)
Выполняет обход по цитируемым выражениям в глубину, используя накопитель.
- underscore(atom_or_string)
Преобразует данный аргумент в строку с форматом подчёркивание-слеш.
- unescape_string(string)
Деэкранирует символы в строке.
- unescape_string(string, map)
Деэкранирует символы в строке в соответствии с заданным отображением.
- unique_var(var, context)
Генерирует узел AST, представляющий уникальную переменную, заданную атомами
varиcontext.- unpipe(expr)
Разбивает выражение конвейера на список.
- update_meta(quoted, fun)
Применяет данную функцию к метаданным узла, если они содержатся.
- validate(expr)
Проверяет, являются ли данные выражения допустимыми цитируемыми выражениями.
- var(var, context)
Генерирует узел AST, представляющий переменную, заданную атомами
varиcontext.
Типы
captured_remote_function()Source
@type captured_remote_function() :: (... -> any())
Захваченная удалённая функция в формате &Mod.fun/arity
input()Source
@type input() ::
input_expr() | {input(), input()} | [input()] | atom() | number() | binary() Входы макроса
metadata()Source
@type metadata() :: keyword()
Список ключевых слов метаданных AST.
Метаданные в AST Elixir — это список ключевых слов значений. Можно использовать любые ключи, и разные части компилятора могут использовать разные ключи. Например, AST, полученный макросом, всегда будет содержать аннотацию :line, а AST, выпущенный quote/2, будет содержать аннотацию :line только в том случае, если предоставлен параметр :line.
Следующие ключи метаданных являются публичными:
-
:context- Определяет контекст, в котором был сгенерирован AST. Например,quote/2будет включать модуль, вызывающийquote/2, в качестве контекста. Это часто используется для различения обычного кода от кода, сгенерированного макросом илиquote/2. -
:counter- Счётчик переменных, используемый для гигиены переменных. С точки зрения компилятора, каждая переменная идентифицируется комбинацией либоnameиmetadata[:counter], либоnameиcontext. -
:generated- Указывает, следует ли рассматривать код как сгенерированный компилятором. Это означает, что компилятор и инструменты, такие как Dialyzer, могут не выдавать определённые предупреждения. -
:if_undefined- Способ расширения неопределённой переменной. Установите его в значение:apply, если вы хотите, чтобы переменная стала нульарным вызовом без предупреждения. -
:keep- Используетсяquote/2с параметромlocation: :keepдля аннотации файла и номера строки исходного кода. -
:line- Номер строки узла AST.
Следующие ключи метаданных активируются Code.string_to_quoted/2:
-
:closing- содержит метаданные о закрывающей паре, такой как}в кортеже или в карте, или закрывающем)в вызове функции с круглыми скобками.:closingне ограничивает конец выражения, если существуют метаданные:doи:end(когда:token_metadataимеет значение true). -
:column- номер столбца узла AST (если:columnsимеет значение true). -
:delimiter- содержит открывающую разделитель для сигилов, строк и списков символов в виде строки (таких как"{","/","'", и тому подобное). -
:format- устанавливается в значение:keywordпри определении атома как ключевого слова. -
:do- содержит метаданные об расположенииdoв вызове функции с блокамиdo-end(если:token_metadataимеет значение true). -
:end- содержит метаданные об расположенииendв вызове функции с блокамиdo-end(если:token_metadataимеет значение true). -
:end_of_expression- обозначает момент, когда фактически происходит конец выражения. Доступно для всех выражений, кроме последнего внутри__block__(если:token_metadataимеет значение true). -
:indentation- отступ сигила heredoc.
Следующие ключи метаданных являются закрытыми:
-
:alias- Используется для гигиены псевдонимов. -
:ambiguous_op- Используется для улучшения сообщений об ошибках в компиляторе. -
:imports- Используется для гигиены импорта. -
:var- Используется для улучшения сообщений об ошибках при неопределённых переменных.
Не полагайтесь на них, так как они могут измениться или быть полностью удалены в будущих версиях языка. Они часто используются quote/2 и компилятором для предоставления функций, таких как гигиена, улучшенные сообщения об ошибках и так далее.
Если вы вводите пользовательские ключи в метаданные AST, убедитесь, что вы префиксните их именем вашей библиотеки или приложения, чтобы они не конфликтовали с ключами, которые потенциально могут быть введены компилятором в будущем.
output()Source
@type output() ::
output_expr()
| {output(), output()}
| [output()]
| atom()
| number()
| binary()
| captured_remote_function()
| pid() Вывод макроса
t()Source
@type t() :: input()
Дерево абстрактного синтаксиса (AST)
Функции
camelize(строка)Исходный код
@spec camelize(String.t()) :: String.t()
Преобразует заданную строку в формат CamelCase.
Эта функция была разработана для преобразования идентификаторов/токенов языка в CamelCase, поэтому она принадлежит модулю Macro. Не используйте её как общий механизм для преобразования строк в CamelCase, так как она не поддерживает Unicode или символы, которые не являются допустимыми в Elixir-идентификаторах.
Примеры
iex> Macro.camelize("foo_bar")
"FooBar"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
Если присутствуют заглавные буквы, они никак не изменяются в качестве механизма сохранения аббревиатур:
iex> Macro.camelize("API.V1")
"API.V1"
iex> Macro.camelize("API_SPEC")
"API_SPEC" classify_atom(атом)Исходный код
@spec classify_atom(atom()) :: :alias | :identifier | :quoted | :unquoted
Классифицирует atom в выполнении на основе его возможного размещения в AST.
Возвращает один из следующих атомов:
:alias— атом представляет собой псевдоним:identifier— атом может использоваться в качестве переменной или локального вызова функции (а также быть необрамлённым атомом):unquoted— атом может использоваться в его необрамлённом виде, включает операторы и атомы с@в них:quoted— все остальные атомы, которые могут использоваться только в своей обрамлённой форме
Большинство операторов будут :unquoted, например :+, за некоторыми исключениями, которые возвращают :quoted из-за неоднозначности, например :"::". Используйте operator?/2 для проверки, является ли данный атом оператором.
Примеры
iex> Macro.classify_atom(:foo) :identifier iex> Macro.classify_atom(Foo) :alias iex> Macro.classify_atom(:foo@bar) :unquoted iex> Macro.classify_atom(:+) :unquoted iex> Macro.classify_atom(:Foo) :unquoted iex> Macro.classify_atom(:"with spaces") :quoted
dbg(код, опции, среда)Исходный код
@spec dbg(t(), t(), Macro.Env.t()) :: t()
По умолчанию, бэкенд для Kernel.dbg/2.
Эта функция предоставляет бэкенд по умолчанию для Kernel.dbg/2. Подробнее см. документацию Kernel.dbg/2.
Эта функция:
- выводит информацию о заданном
env - выводит информацию о
codeи возвращаемом значении (используяoptsдля проверки терминов) - возвращает значение, возвращённое при вычислении
code
Вы можете вызвать эту функцию напрямую для создания бэкендов Kernel.dbg/2, которые обращаются к этой функции.
decompose_call(ast)Исходный код
@spec 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
iex> Macro.decompose_call(quote(do: {:foo, [], []}))
:error escape(выражение, опции \\ [])Исходный код
@spec 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должен хранить. Сохраняется только минимальный объём метаданных, таких как:lineи:no_parens.
Сравнение с quote/2
Функция escape/2 иногда путают с 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 используется для экранирования значений (либо непосредственно переданных, либо связанных переменной), а quote/2 создаёт деревья синтаксического анализа для выражений.
expand(ast, среда)Исходный код
@spec expand(t(), Macro.Env.t()) :: t()
Принимает узел AST и расширяет его до тех пор, пока он больше не может быть расширен.
Обратите внимание, что эта функция не проходит по дереву AST, а расширяет только корневой узел.
Эта функция использует expand_once/2 внутри. Проверьте её для получения дополнительной информации и примеров.
expand_literals(ast, среда)Исходный код
@spec expand_literals(t(), Macro.Env.t()) :: t()
Расширяет все литералы в ast с заданной env.
Эта функция в основном используется для удаления зависимостей времени компиляции из узлов AST. В таких случаях заданная среда обычно используется для представления функции:
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
В настоящее время единственными расширяемыми литеральными узлами в AST являются псевдонимы, поэтому эта функция расширяет только псевдонимы.
Однако будьте осторожны при удалении зависимостей времени компиляции между модулями. Если вы их удалите, но всё ещё вызываете модуль в момент компиляции, Elixir не сможет корректно перекомпилировать модули при их изменении.
expand_literals(ast, acc, функция)Исходный код
@spec expand_literals(t(), acc, (t(), acc -> {t(), acc})) :: t() when acc: term() Расширяет все литералы в ast с заданной acc и fun.
fun будет вызвана с расширяемым узлом AST и acc, и должна вернуть новый узел с acc. Это обобщённая версия expand_literals/2, которая поддерживает пользовательскую функцию расширения. Пожалуйста, обратитесь к expand_literals/2 для примеров использования и ловушек.
expand_once(ast, среда)Исходный код
@spec expand_once(t(), Macro.Env.t()) :: t()
Принимает узел 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(amount, context)Source
@spec generate_arguments(0, context :: atom()) :: []
@spec generate_arguments(pos_integer(), context) :: [{atom(), [], context}, ...]
when context: atom() Генерирует узлы AST для заданного числа переменных аргументов, используя Macro.var/2.
Обратите внимание, что аргументы не уникальны. Если впоследствии вам нужно получить доступ к тем же переменным, вы можете вызвать эту функцию с теми же входными данными. Используйте generate_unique_arguments/2 для генерации уникальных аргументов, которые нельзя перезаписать.
Примеры
iex> Macro.generate_arguments(2, __MODULE__)
[{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}] generate_unique_arguments(amount, context)Source
@spec generate_unique_arguments(0, context :: atom()) :: []
@spec generate_unique_arguments(pos_integer(), context) :: [
{atom(), [{:counter, integer()}], context},
...
]
when context: atom() Генерирует узлы AST для заданного числа переменных аргументов, используя Macro.unique_var/2.
Примеры
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
iex> {:arg1, [counter: c1], __MODULE__} = var1
iex> {:arg2, [counter: c2], __MODULE__} = var2
iex> is_integer(c1) and is_integer(c2)
true inspect_atom(source_format, atom)Source
@spec inspect_atom(:literal | :key | :remote_call, atom()) :: binary()
Проверяет atom в соответствии с различными форматами исходного кода.
Атом можно проверить в соответствии с тремя различными форматами, в которых он встречается в AST: как литерал (:literal), как ключ (:key) или как имя функции удалённого вызова (:remote_call).
Примеры
Как литерал
Литералы включают обычные атомы, процитированные атомы, операторы, псевдонимы и специальные nil, true, и false атомы.
iex> Macro.inspect_atom(:literal, nil) "nil" iex> Macro.inspect_atom(:literal, :foo) ":foo" iex> Macro.inspect_atom(:literal, :<>) ":<>" iex> Macro.inspect_atom(:literal, :Foo) ":Foo" iex> Macro.inspect_atom(:literal, Foo.Bar) "Foo.Bar" iex> Macro.inspect_atom(:literal, :"with spaces") ":\"with spaces\""
Как ключ
Проверка атома в качестве ключа списка ключевых слов или карты.
iex> Macro.inspect_atom(:key, :foo) "foo:" iex> Macro.inspect_atom(:key, :<>) "<>:" iex> Macro.inspect_atom(:key, :Foo) "Foo:" iex> Macro.inspect_atom(:key, :"with spaces") "\"with spaces\":"
Как имя удалённого вызова
Проверка атома как имени функции удалённого вызова.
iex> Macro.inspect_atom(:remote_call, :foo) "foo" iex> Macro.inspect_atom(:remote_call, :<>) "<>" iex> Macro.inspect_atom(:remote_call, :Foo) "\"Foo\"" iex> Macro.inspect_atom(:remote_call, :"with spaces") "\"with spaces\""
operator?(name, arity)Source
@spec operator?(name :: atom(), arity()) :: boolean()
Возвращает true, если заданное имя и арность являются оператором.
Примеры
iex> Macro.operator?(:not_an_operator, 3) false iex> Macro.operator?(:.., 0) true iex> Macro.operator?(:+, 1) true iex> Macro.operator?(:++, 2) true iex> Macro.operator?(:..//, 3) true
path(ast, fun)Source
Возвращает путь к узлу в ast, в котором fun возвращает true.
Путь является списком, начинающимся с узла, в котором fun возвращает true, за которым следуют все его родительские узлы.
Вычисление пути может быть эффективной операцией, когда вы хотите найти определённый узел в AST в его контексте и затем утверждать что-то о нём.
Примеры
iex> Macro.path(quote(do: [1, 2, 3]), & &1 == 3)
[3, [1, 2, 3]]
iex> Macro.path(quote(do: Foo.bar(3)), & &1 == 3)
[3, quote(do: Foo.bar(3))]
iex> Macro.path(quote(do: %{foo: [bar: :baz]}), & &1 == :baz)
[
:baz,
{:bar, :baz},
[bar: :baz],
{:foo, [bar: :baz]},
{:%{}, [], [foo: [bar: :baz]]}
] pipe(expr, call_args, position)Source
@spec pipe(t(), t(), integer()) :: t()
Передаёт expr в call_args в заданной position.
Эта функция может использоваться для реализации функциональности типа |>. Например, сам |> реализован как:
defmacro left |> right do Macro.pipe(left, right, 0) end
expr — это AST выражения. call_args должен быть AST вызова, иначе эта функция вызовет ошибку. Например, рассмотрим оператор конвейера |>/2, который использует эту функцию для построения конвейеров.
Даже если выражение передаётся в AST, это не обязательно означает, что AST является корректным. Например, вы можете передать аргумент в div/2, фактически превратив его в вызов div/3, которая является функцией, которая по умолчанию не существует. Код вызовет ошибку, если функция div/3 не определена локально.
postwalk(ast, fun)Source
@spec postwalk(t(), (t() -> t())) :: t()
Эта функция работает как prewalk/2, но выполняет обход цитируемых выражений в глубину, в пост-порядке.
postwalk(ast, acc, fun)Source
@spec postwalk(t(), any(), (t(), any() -> {t(), any()})) :: {t(), any()} Эта функция работает как prewalk/3, но выполняет обход цитируемых выражений в глубину, в пост-порядке, используя накопитель.
postwalker(ast)Source
@spec postwalker(t()) :: Enumerable.t()
Возвращает перечислимый объект, который обходит ast в глубину, в пост-порядке.
Примеры
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.postwalker(ast), & &1)
[1, "abc", {:foo, [], [1, "abc"]}] prewalk(ast, fun)Source
@spec prewalk(t(), (t() -> t())) :: t()
Выполняет обход цитируемых выражений в глубину, в пред-порядке.
Возвращает новый AST, где каждый узел является результатом вызова fun на соответствующем узле ast.
Примеры
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> new_ast = Macro.prewalk(ast, fn
...> {:+, meta, children} -> {:*, meta, children}
...> {:*, meta, children} -> {:+, meta, children}
...> other -> other
...> end)
iex> {:*, _, [5, {:+, _, [3, 7]}]} = new_ast
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []} prewalk(ast, acc, fun)Source
@spec prewalk(t(), any(), (t(), any() -> {t(), any()})) :: {t(), any()} Выполняет обход цитируемых выражений в глубину, в пред-порядке, используя накопитель.
Возвращает кортеж, где первый элемент — новый AST, где каждый узел является результатом вызова fun на соответствующем узле, а второй — конечный накопитель.
Примеры
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} = Macro.prewalk(ast, [], fn
...> {:+, meta, children}, acc -> {{:*, meta, children}, [:+ | acc]}
...> {:*, meta, children}, acc -> {{:+, meta, children}, [:* | acc]}
...> other, acc -> {other, acc}
...> end)
iex> {{:*, _, [5, {:+, _, [3, 7]}]}, [:*, :+]} = {new_ast, acc}
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []} prewalker(ast)Source
@spec prewalker(t()) :: Enumerable.t()
Возвращает перечислимый объект, который обходит ast в глубину, в пред-порядке.
Примеры
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.prewalker(ast), & &1)
[{:foo, [], [1, "abc"]}, 1, "abc"] quoted_literal?(term)Source
@spec quoted_literal?(t()) :: boolean()
Возвращает true, если данное цитируемое выражение представляет цитируемый литерал.
Атомы и числа всегда являются литералами. Бинарные данные, списки, кортежи, карты и структуры являются литералами только в том случае, если все их элементы также являются литералами.
Примеры
iex> Macro.quoted_literal?(quote(do: "foo"))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1}))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1, :baz}))
true
iex> Macro.quoted_literal?(quote(do: %{foo: "bar"}))
true
iex> Macro.quoted_literal?(quote(do: %URI{path: "/"}))
true
iex> Macro.quoted_literal?(quote(do: URI.parse("/")))
false
iex> Macro.quoted_literal?(quote(do: {foo, var}))
false special_form?(name, arity)Source
@spec special_form?(name :: atom(), arity()) :: boolean()
Возвращает true если данное имя и арность являются специальной формой.
struct!(module, env)Source
@spec struct!(module, Macro.Env.t()) :: %{
:__struct__ => module,
optional(atom()) => any()
}
when module: module() Расширяет структуру, заданную module в заданном env.
Это полезно, когда структура должна быть расширена во время компиляции, а структура, которая расширяется, может быть или не быть скомпилированной. Эта функция также способна расширять структуры, определённые в модуле, который компилируется.
Она вызовет CompileError если структура недоступна. Начиная с Elixir v1.12, вызов этой функции также добавляет зависимость экспорта на заданную структуру.
to_string(tree)Source
@spec to_string(t()) :: String.t()
Преобразует данный выражение AST в строку.
Это удобная функция для преобразования AST в строку, которая отбрасывает всю форматирование исходного кода и добавляет новые строки вокруг 98 символов. См. Code.quoted_to_algebra/2 как функцию более низкого уровня с большим контролем над форматированием.
Примеры
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3))) "foo.bar(1, 2, 3)"
to_string(tree, fun)Source
@spec to_string(t(), (t(), String.t() -> String.t())) :: String.t()
Преобразует заданное выражение AST в строку.
Указанная fun вызывается для каждого узла в AST с двумя аргументами: AST узла, который печатается, и строковое представление этого же узла. Возвращаемое значение этой функции используется в качестве окончательного строкового представления узла AST.
Эта функция отбрасывает все форматирования исходного кода.
Примеры
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)Source
@spec traverse(t(), any(), (t(), any() -> {t(), any()}), (t(), any() -> {t(), any()})) ::
{t(), any()} Выполняет обход выражений в кавычках по методу «глубина-в-ширину» с использованием аккумулятора.
Возвращает кортеж, где первый элемент — новый AST, а второй — конечный аккумулятор. Новый AST является результатом вызова pre для каждого узла ast в фазе «предобработка» и post в фазе «послеобработка».
Примеры
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} =
...> Macro.traverse(
...> ast,
...> [],
...> fn
...> {:+, meta, children}, acc -> {{:-, meta, children}, [:- | acc]}
...> {:*, meta, children}, acc -> {{:/, meta, children}, [:/ | acc]}
...> other, acc -> {other, acc}
...> end,
...> fn
...> {:-, meta, children}, acc -> {{:min, meta, children}, [:min | acc]}
...> {:/, meta, children}, acc -> {{:max, meta, children}, [:max | acc]}
...> other, acc -> {other, acc}
...> end
...> )
iex> {:min, _, [5, {:max, _, [3, 7]}]} = new_ast
iex> [:min, :max, :/, :-] = acc
iex> Code.eval_quoted(new_ast)
{5, []} underscore(atom_or_string)Source
@spec underscore(module() | 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"
iex> Macro.camelize("foo/bar")
"Foo.Bar" unescape_string(string)Source
@spec 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 и другие), которые получают необработанную, неэкранированную строку, и её можно использовать в любом месте, где необходимо имитировать обработку строк в Elixir.
Примеры
iex> Macro.unescape_string("example\\n")
"example\n"
В примере выше мы передаём строку с \n экранированным и возвращаем версию с ним раэкранированным.
unescape_string(string, map)Source
@spec unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) :: String.t()
Разворачивает символы в строке согласно заданному отображению.
Смотрите unescape_string/1, если вы хотите использовать ту же карту экранирования, что и в одинарных и двойных кавычках Elixir.
Функция отображения
Функция отображения получает целое число, представляющее кодовую точку символа, который нужно раэкранировать. Также есть специальные атомы :newline, :unicode, и :hex, которые управляют символами новой строки, Unicode и экранирования соответственно.
Вот функция отображения по умолчанию, реализованная в Elixir:
def unescape_map(:newline), do: true 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, символ не экранируется, и обратная косая черта сохраняется в строке.
Примеры
Использование функции unescape_map/1 определённой выше — просто:
Macro.unescape_string("example\\n", &unescape_map(&1)) unique_var(var, context)Source
@spec unique_var(var, context) :: {var, [{:counter, integer()}], context}
when var: atom(), context: atom() Генерирует узел AST, представляющий уникальную переменную, заданную атомами var и context.
Вызов этой функции с теми же аргументами сгенерирует другую переменную с собственным уникальным счётчиком. См. var/2 для альтернативы.
Примеры
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
iex> is_integer(c)
true unpipe(expr)Source
@spec unpipe(t()) :: [t()]
Разбивает выражение канала в список.
AST для канала (последовательности применений |>/2) похож на AST последовательности бинарных операторов или приложений функций: выражение верхнего уровня — это самый правый :|> (который выполняется последним), а его левая и правая части — это его аргументы:
quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]}
В примере выше, канал |>/2 является самым правым каналом; 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)Source
@spec 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)Source
@spec 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)Source
@spec var(var, context) :: {var, [], context} when var: atom(), context: atom() Генерирует узел AST, представляющий переменную, заданную атомами var и context.
Обратите внимание, что эта переменная не уникальна. Если вам нужно позже получить доступ к этой же переменной, вы можете снова вызвать var/2 с теми же аргументами. Используйте unique_var/2 для генерации уникальной переменной, которую нельзя переопределить.
Примеры
Для создания переменной требуется контекст. Большинство раз, для сохранения гигиены, контекст должен быть __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.14.1/Macro.html