Исходный код Макросы
Функции для работы с абстрактным синтаксическим деревом (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.
Краткое описание
Типы
- captured_remote_function()
Захваченная удалённая функция в формате &Mod.fun/arity
- input()
Входы макроса
- metadata()
Список ключевых слов метаданных AST.
- output()
Выход макроса
- t()
Абстрактное синтаксическое дерево (AST)
Функции
- camelize(строка)
Преобразует заданную строку в формат CamelCase.
- classify_atom(атом)
Классифицирует
atomна основе возможного расположения в AST.- compile_apply(mod, fun, args, caller)
Применяет
mod,function, иargsво время компиляции вcaller.- dbg(код, опции, env)
По умолчанию бэкэнд для
Kernel.dbg/2.- decompose_call(ast)
Декомпозирует локальный или удалённый вызов на его удалённую часть (если имеется), имя функции и список аргументов.
- escape(выражение, опции \\ [])
Рекурсивно экранирует значение, чтобы его можно было вставить в синтаксическое дерево.
- 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(количество, контекст)
Генерирует узлы AST для заданного количества требуемых переменных аргументов, используя
Macro.var/2.- generate_unique_arguments(количество, контекст)
Генерирует узлы AST для заданного количества требуемых переменных аргументов, используя
Macro.unique_var/2.- inspect_atom(формат_источника, атом)
Проверяет
atomв соответствии с различными форматами исходного кода.- operator?(имя, арность)
Возвращает
true, если заданное имя и арность являются оператором.- path(ast, fun)
Возвращает путь к узлу в
ast, для которогоfunвозвращает истинное значение.- pipe(выражение, аргументы_вызова, позиция)
Направляет
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?(термин)
Возвращает
true, если заданное цитируемое выражение представляет цитируемый литерал.- special_form?(имя, арность)
Возвращает
true, если заданное имя и арность являются специальной формой.- struct!(модуль, env)
Расширяет структуру, заданную
moduleв заданномenv.- to_string(дерево)
Преобразует данное выражение AST в строку.
- to_string(дерево, функция) устаревшая
Преобразует данное выражение AST в строку.
- traverse(ast, acc, pre, post)
Выполняет обход цитируемых выражений в глубину с использованием аккумулятора.
- underscore(атом_или_строка)
Преобразует заданный аргумент в строку с использованием формата подчёркивание-слэш.
- unescape_string(строка)
Раскрашивает символы в строке.
- unescape_string(строка, отображение)
Раскрашивает символы в строке согласно заданному отображению.
- unique_var(переменная, контекст)
Генерирует узел AST, представляющий уникальную переменную, заданную атомами
varиcontext.- unpipe(выражение)
Разбивает выражение конвейера на список.
- update_meta(цитирование, функция)
Применяет заданную функцию к метаданным узла, если они содержатся.
- validate(выражение)
Проверяет, что заданные выражения являются допустимыми цитируемыми выражениями.
- var(переменная, контекст)
Генерирует узел 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.
Метаданные в Elixir AST — это список ключевых слов значений. Можно использовать любые ключи, и разные части компилятора могут использовать разные ключи. Например, AST, полученный макросом, всегда будет содержать аннотацию :line, а AST, выпущенный quote/2, будет содержать аннотацию :line только при предоставлении опции :line.
Следующие ключи метаданных являются общедоступными:
:context— Определяет контекст, в котором был сгенерирован AST. Например,quote/2будет включать модуль, вызывающийquote/2, как контекст. Это часто используется для различения обычного кода от кода, сгенерированного макросом илиquote/2.:counter— Счётчик переменных, используемый для гигиены переменных. С точки зрения компилятора, каждая переменная идентифицируется сочетанием либоnameиmetadata[:counter], либоnameиcontext.:from_brackets— Используется для определения того, происходит ли вызовAccess.get/3из синтаксиса в квадратных скобках.:from_interpolation— Используется для определения того, происходит ли вызовKernel.to_string/1из интерполяции.:generated— Указывает, следует ли рассматривать код как сгенерированный компилятором. Это означает, что компилятор и инструменты, такие как Dialyzer, могут не генерировать определённые предупреждения.:if_undefined— Как расширять неопределённую переменную. Установите значение:apply, если вы хотите, чтобы переменная стала вызовом без аргументов без предупреждения, или:raise.:keep— Используетсяquote/2с опциейlocation: :keepдля аннотирования файла и номера строки исходного кода.:line— Номер строки узла AST. Обратите внимание, что информация о строках удаляется из цитируемого кода, но может быть снова включена с помощью опции:line.
Следующие ключи метаданных активируются Code.string_to_quoted/2:
:closing— содержит метаданные об закрывающей паре, например, о закрывающем}в кортеже или в карте, или о закрывающем)в вызове функции с круглыми скобками (когда:token_metadataимеет значение true). Если к вызову функции прикреплён блок do-end, его метаданные находятся в метаданных:doи:end.:column— номер столбца узла AST (если:columnsимеет значение true). Обратите внимание, что информация о столбцах всегда удаляется из цитируемого кода.:delimiter— содержит открывающий разделитель для сигилов, строк и списков символов в виде строки (например,"{","/","'", и т. д.):format— имеет значение:keyword, когда атом определён как ключевое слово.:do— содержит метаданные об расположенииdoв вызове функции с блокамиdo-end(если:token_metadataимеет значение true).:end— содержит метаданные об расположенииendв вызове функции с блокамиdo-end(если:token_metadataимеет значение true).:end_of_expression— обозначает, когда фактически происходит окончание выражения (если:token_metadataимеет значение true). Это доступно только для выражений внутри «блоков кода», которые являются либо прямыми дочерними элементами__block__, либо правой частью->. Последнее выражение блока не имеет метаданных, если за ним не следует символ конца строки (новая строка или;).: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
compile_apply(mod, fun, args, caller)Источник
Применяет mod, function, и args во время компиляции в caller.
Это используется, когда вы хотите программно вызвать макрос во время компиляции.
dbg(код, опции, env)Источник
@spec dbg(t(), t(), Macro.Env.t()) :: t()
По умолчанию обработчик для Kernel.dbg/2.
Эта функция предоставляет обработчик по умолчанию для Kernel.dbg/2. Дополнительную информацию см. в документации к Kernel.dbg/2.
Эта функция:
- выводит информацию о заданном
env - выводит информацию об
codeи его возвращаемом значении (используяoptsдля проверки терминов) - возвращает значение, возвращенное при вычислении
code
Вы можете напрямую вызвать эту функцию для создания обработчиков Kernel.dbg/2, которые обращаются к этой функции по умолчанию.
Эта функция вызывает исключение, если контекст заданного env является :match или :guard.
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, env)Источник
@spec expand(input(), Macro.Env.t()) :: output()
Принимает узел AST и расширяет его до тех пор, пока больше не удаётся.
Обратите внимание, что эта функция не обходит AST, только корневой узел расширяется.
Эта функция использует expand_once/2 внутри. Посмотрите её для получения дополнительной информации и примеров.
expand_literals(ast, env)Источник
@spec expand_literals(input(), Macro.Env.t()) :: output()
Расширяет все литералы в ast с использованием предоставленного env.
Эта функция в основном используется для удаления зависимостей от компиляции из узлов AST. В таких случаях предоставленная среда обычно используется для представления функции:
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
В настоящее время единственными расширяемыми литеральными узлами в AST являются псевдонимы, поэтому эта функция расширяет только псевдонимы (и делает это везде в литерале).
Однако будьте осторожны при удалении зависимостей от компиляции между модулями. Если вы их удалите, но всё ещё вызываете модуль во время компиляции, Elixir не сможет правильно перекомпилировать модули при их изменении.
expand_literals(ast, acc, fun)Источник
@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, env)Source
@spec expand_once(input(), Macro.Env.t()) :: output()
Принимает узел AST и расширяет его один раз.
Следующее содержимое расширяется:
- Макросы (локальные или удалённые)
- Псевдонимы расширяются (если возможно) и возвращают атомы
- Макросы среды компиляции (
__CALLER__/0,__DIR__/0,__ENV__/0и__MODULE__/0) - Чтение атрибутов модуля (
@foo)
Если выражение нельзя расширить, оно возвращает само выражение. Эта функция не обходит AST, только корневой узел расширяется. Расширение происходит так, как если бы оно расширялось компилятором Elixir, и поэтому во время расширения будут вызываться отслеживатели компиляции и будут выводиться предупреждения об устаревших функциях.
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
@spec path(t(), (t() -> as_boolean(term()))) :: [t()] | nil
Возвращает путь к узлу в ast, для которого fun возвращает истинное значение.
Путь — это список, начинающийся с узла, в котором fun возвращает истинное значение, за которым следуют все его родительские узлы.
Возвращает nil если fun возвращает только ложные значения.
Вычисление пути может быть эффективной операцией, когда вы хотите найти конкретный узел в AST в его контексте и затем утверждать что-либо о нём.
Примеры
iex> Macro.path(quote(do: [1, 2, 3]), & &1 == 3)
[3, [1, 2, 3]]
iex> Macro.path(quote(do: [1, 2]), & &1 == 5)
nil
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, которые управляют новой строкой, юникодом и экранированием, соответственно.
Вот функция отображения по умолчанию, реализованная в 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-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.17.2/Macro.html