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.
Пользовательские сигилы
Макросы также часто используются для реализации пользовательских сигилов.
Сигилы начинаются с ~ и следуют за одной строчной буквой или одной или несколькими заглавными буквами, а затем разделителем (см. Справочник по синтаксису). Пример — ~D[2020-10-13] для определения даты.
Для создания пользовательского сигила определите макрос с именем sigil_{identifier}, который принимает два аргумента. Первый аргумент — строка, второй — список символов, содержащих любые модификаторы. Если сигил имеет строчную букву (например, sigil_x), то строковый аргумент допускает интерполяцию. Если сигил состоит из одной или нескольких заглавных букв (например, sigil_X и sigil_EXAMPLE), то строка не будет интерполироваться.
Допустимые модификаторы — это буквы и цифры ASCII. Любой другой символ вызовет синтаксическую ошибку.
Сигилы с одной буквой обычно зарезервированы для языка. Сигилы с несколькими буквами записываются заглавными буквами и широко используются сообществом для встраивания альтернативных разметки и типов данных в исходный код Elixir.
Модуль, содержащий пользовательский сигил, должен быть импортирован, прежде чем синтаксис сигила можно будет использовать.
Примеры
В качестве примера давайте определим сигил ~x и сигил ~X, которые возвращают своё содержимое в виде строки. Однако, если указан модификатор r, он вместо этого инвертирует строку:
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)
Классифицирует
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())
Захваченная удалённая функция в формате &Модуль.функция/арность
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.:generated— Указывает, считается ли код сгенерированным компилятором. Это означает, что компилятор и инструменты, такие как Dialyzer, могут не выдавать определённые предупреждения.:if_undefined— Как расширить неопределённую переменную. Установите:apply, если вы хотите, чтобы переменная стала вызовом нулевой арности без предупреждения, или:raise.:keep— Используетсяquote/2с опциейlocation: :keepдля аннотирования файла и номера строки цитируемого источника.:line— Номер строки узла AST.:from_brackets— Используется для определения, исходит ли вызовAccess.get/3из синтаксиса скобок или вызова функции.
Следующие ключи метаданных включены 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(string)Source
@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(atom)Source
@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(code, options, env)Source
@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)Source
@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(expr, opts \\ [])Source
@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)Source
@spec expand(input(), Macro.Env.t()) :: output()
Получает узел AST и расширяет его до тех пор, пока это больше не возможно.
Обратите внимание, что эта функция не просматривает AST, только корневой узел расширяется.
Эта функция использует expand_once/2 под капотом. Ознакомьтесь с ней для получения дополнительной информации и примеров.
expand_literals(ast, env)Source
@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)Source
@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, расширяется только корневой узел.
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. Не используйте ее как общий механизм для подчеркивания строк, так как она не поддерживает Юникод или символы, которые не являются допустимыми идентификаторами 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, а кодовые точки Юникода как \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 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.15.4/Macro.html