Spec-Zone.ru › Elixir 1.7

Макрос

Удобства работы с макросами.

Пользовательские знаки

Для создания пользовательского знака, определите функцию с именем sigil_{identifier} , которая принимает два аргумента. Первый аргумент — строка, второй — список символов, содержащих любые модификаторы. Если знак в нижнем регистре (например, sigil_x) , то аргумент строки позволит выполнить интерполяцию. Если знак в верхнем регистре (например, sigil_X) , то строка не будет интерполироваться.

Допустимые модификаторы включают только буквы в нижнем и верхнем регистре. Другие символы приведут к синтаксической ошибке.

Модуль, содержащий пользовательский знак, должен быть импортирован, прежде чем можно будет использовать синтаксис знака.

Примеры

defmodule MySigils do
  defmacro sigil_x(term, [?r]) do
    quote do
      unquote(term) |> String.reverse()
    end
  end
  defmacro sigil_x(term, _modifiers) do
    term
  end
  defmacro sigil_X(term, [?r]) do
    quote do
      unquote(term) |> String.reverse()
    end
  end
  defmacro sigil_X(term, _modifiers) do
    term
  end
end

import MySigils

~x(with #{"inter" <> "polation"})
#=>"with interpolation"

~x(with #{"inter" <> "polation"})r
#=>"noitalopretni htiw"

~X(without #{"interpolation"})
#=>"without \#{"interpolation"}"

~X(without #{"interpolation"})r
#=>"}\"noitalopretni\"{# tuohtiw"

Резюме

Типы

expr()

Представляет выражения в AST

literal()

Представляет литералы в AST

t()

Абстрактное синтаксическое дерево (AST)

Функции

camelize(строка)

Преобразует заданную строку в формат CamelCase

decompose_call(ast)

Декомпозирует локальный или удалённый вызов на его удалённую часть (если она предоставлена), имя функции и список аргументов

escape(expr, opts \\ [])

Рекурсивно экранирует значение, чтобы его можно было вставить в синтаксическое дерево

expand(дерево, env)

Принимает узел AST и расширяет его до тех пор, пока это больше невозможно

expand_once(ast, env)

Принимает узел AST и расширяет его один раз

generate_arguments(количество, context)

Генерирует узлы AST для заданного количества требуемых переменных аргументов с использованием Macro.var/2

operator?(имя, арность)

Возвращает true , если данное имя и арность являются оператором

pipe(expr, call_args, position)

Перенаправляет expr в call_args в заданной position

postwalk(ast, fun)

Выполняет обход в глубину, постфиксный обход выражений в кавычках

postwalk(ast, acc, fun)

Выполняет обход в глубину, постфиксный обход выражений в кавычках с использованием накопителя

prewalk(ast, fun)

Выполняет обход в глубину, префиксный обход выражений в кавычках

prewalk(ast, acc, fun)

Выполняет обход в глубину, префиксный обход выражений в кавычках с использованием накопителя

quoted_literal?(термин)

Возвращает true , если данное выражение в кавычках является литералом AST

special_form?(имя, арность)

Возвращает true , если данное имя и арность являются специальной формой

to_string(дерево, fun \\ fn _ast, string -> string end)

Преобразует данное выражение AST в строку

traverse(ast, acc, pre, post)

Выполняет обход в глубину выражений в кавычках с использованием накопителя

underscore(атом)

Преобразует заданный атом или двоичное значение в формат с нижним подчеркиванием

unescape_string(символы)

Раскладывает заданные символы

unescape_string(символы, карта)

Раскладывает заданные символы в соответствии с указанной картой

unpipe(expr)

Разбивает выражение конвейера на список

update_meta(quoted, fun)

Применяет данную функцию к метаданным узла, если они присутствуют

validate(expr)

Проверяет, являются ли заданные выражения валидными выражениями в кавычках

var(переменная, context)

Генерирует узел AST, представляющий переменную, заданную атомами var и context

Типы

expr()

expr() :: {expr() | atom(), keyword(), atom() | [t()]}

Представляет выражения в AST

literal()

literal() ::
  atom() | number() | binary() | (... -> any()) | {t(), t()} | [t()]

Представляет литералы в AST

t()

t() :: expr() | literal()

Абстрактное синтаксическое дерево (AST)

Функции

camelize(строка)

camelize(String.t()) :: String.t()

Преобразует заданную строку в формат CamelCase.

Эта функция была разработана для преобразования идентификаторов/токенов языка в CamelCase, поэтому она принадлежит модулю Macro. Не используйте её как общий механизм преобразования строк в CamelCase, так как она не поддерживает Unicode или символы, которые не являются допустимыми в идентификаторах Elixir.

Примеры

iex> Macro.camelize("foo_bar")
"FooBar"

Если присутствуют символы в верхнем регистре, они не изменяются, чтобы сохранить аббревиатуры:

iex> Macro.camelize("API.V1")
"API.V1"
iex> Macro.camelize("API_SPEC")
"API_SPEC"

decompose_call(ast)

decompose_call(Macro.t()) ::
  {atom(), [Macro.t()]} | {Macro.t(), atom(), [Macro.t()]} | :error

Декомпозирует локальный или удалённый вызов на его удалённую часть (если она предоставлена), имя функции и список аргументов.

Возвращает :error при предоставлении некорректного синтаксиса вызова.

Примеры

iex> Macro.decompose_call(quote(do: foo))
{:foo, []}

iex> Macro.decompose_call(quote(do: foo()))
{:foo, []}

iex> Macro.decompose_call(quote(do: foo(1, 2, 3)))
{:foo, [1, 2, 3]}

iex> Macro.decompose_call(quote(do: Elixir.M.foo(1, 2, 3)))
{{:__aliases__, [], [:Elixir, :M]}, :foo, [1, 2, 3]}

iex> Macro.decompose_call(quote(do: 42))
:error

escape(expr, opts \\ [])

escape(term(), keyword()) :: Macro.t()

Рекурсивно экранирует значение, чтобы его можно было вставить в синтаксическое дерево.

Примеры

iex> Macro.escape(:foo)
:foo

iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}

iex> Macro.escape({:unquote, [], [1]}, unquote: true)
1

Параметры

  • :unquote — если true, эта функция оставляет утверждения unquote/1 и unquote_splicing/1 неэкранированными, фактически убирая кавычки из содержимого при экранировании. Этот параметр полезен только при экранировании AST, которые могут содержать фрагменты в кавычках. По умолчанию false.

  • :prune_metadata — если true, удаляет метаданные из экранированных узлов AST. Обратите внимание, что этот параметр изменяет семантику экранированного кода и его следует использовать только при экранировании AST, а не значений. По умолчанию false.

    Например, ExUnit хранит AST каждого утверждения, поэтому при сбое утверждения мы можем показать фрагменты кода пользователям. Без этого параметра каждый раз при компиляции модуля теста мы получаем различный MD5 модуля байткода, потому что AST содержит метаданные, такие как счётчики, специфичные для среды компиляции. При обрезании метаданных мы гарантируем детерминированность модуля и уменьшаем объём данных, которые ExUnit должен хранить.

Сравнение с Kernel.quote/2

Функция escape/2 иногда путают с Kernel.SpecialForms.quote/2, потому что примеры выше ведут себя одинаково с обеими функциями. Ключевое различие лучше всего иллюстрируется, когда значение для экранирования хранится в переменной.

iex> Macro.escape({:a, :b, :c})
{:{}, [], [:a, :b, :c]}
iex> quote do: {:a, :b, :c}
{:{}, [], [:a, :b, :c]}

iex> value = {:a, :b, :c}
iex> Macro.escape(value)
{:{}, [], [:a, :b, :c]}

iex> quote do: value
{:value, [], __MODULE__}

iex> value = {:a, :b, :c}
iex> quote do: unquote(value)
{:a, :b, :c}

escape/2 используется для экранирования значений (прямо переданных или связанных переменными), в то время как Kernel.SpecialForms.quote/2 генерирует синтаксические деревья для выражений.

expand(дерево, env)

Принимает узел AST и расширяет его до тех пор, пока это больше невозможно.

Эта функция использует expand_once/2 под капотом. Обратите внимание на неё для получения дополнительной информации и примеров.

expand_once(ast, env)

Принимает узел AST и расширяет его один раз.

Следующее содержимое расширяется:

  • Макросы (локальные или удалённые)
  • Псевдонимы расширяются (если возможно) и возвращают атомы
  • Макросы среды компиляции (__CALLER__/0, __DIR__/0, __ENV__/0 и __MODULE__/0)
  • Чтение атрибутов модуля (@foo)

Если выражение не может быть расширено, оно возвращает само выражение. Обратите внимание, что expand_once/2 выполняет расширение только один раз, и оно не рекурсивно. Смотрите expand/2 для расширения до тех пор, пока узел больше не может быть расширен.

Примеры

В примере ниже у нас есть макрос, который генерирует модуль с функцией с именем name_length , которая возвращает длину имени модуля. Значение этой функции будет рассчитано во время компиляции, а не во время выполнения.

Рассмотрите реализацию ниже:

defmacro defmodule_with_length(name, do: block) do
  length = length(Atom.to_charlist(name))

  quote do
    defmodule unquote(name) do
      def name_length, do: unquote(length)
      unquote(block)
    end
  end
end

При вызове так:

defmodule_with_length My.Module do
  def other_function, do: ...
end

Компиляция завершится ошибкой, потому что My.Module в кавычках не является атомом, а синтаксическим деревом, как показано ниже:

{:__aliases__, [], [:My, :Module]}

Однако, нам нужно преобразовать узел псевдонимов выше в атом, чтобы мы могли получить его длину. Расширение узла не является тривиальной задачей, так как нам также нужно расширить псевдонимы вызывающего элемента. Например:

alias MyHelpers, as: My

defmodule_with_length My.Module do
  def other_function, do: ...
end

Конечное имя модуля будет MyHelpers.Module а не My.Module. С помощью Macro.expand/2 такие псевдонимы учитываются. Также расширяются локальные и удалённые макросы. Мы можем переписать наш макрос выше, используя эту функцию, как:

defmacro defmodule_with_length(name, do: block) do
  expanded = Macro.expand(name, __CALLER__)
  length   = length(Atom.to_charlist(expanded))

  quote do
    defmodule unquote(name) do
      def name_length, do: unquote(length)
      unquote(block)
    end
  end
end

generate_arguments(amount, context) (с версии 1.5.0)

Генерирует узлы AST для заданного количества переменных аргументов, используя Macro.var/2.

Примеры

iex> Macro.generate_arguments(2, __MODULE__)
[{:var1, [], __MODULE__}, {:var2, [], __MODULE__}]

operator?(name, arity) (с версии 1.7.0)

operator?(name :: atom(), arity()) :: boolean()

Возвращает true если данное имя и арность являются оператором.

pipe(expr, call_args, position)

pipe(Macro.t(), Macro.t(), integer()) :: Macro.t() | no_return()

Перенаправляет expr в call_args в заданной position.

postwalk(ast, fun)

postwalk(t(), (t() -> t())) :: t()

Выполняет обход выражений в кавычках по принципу «обхода в глубину», «постфиксного обхода».

postwalk(ast, acc, fun)

postwalk(t(), any(), (t(), any() -> {t(), any()})) ::
  {t(), any()}

Выполняет обход выражений в кавычках по принципу «обхода в глубину», «постфиксного обхода» с использованием аккумулятора.

prewalk(ast, fun)

prewalk(t(), (t() -> t())) :: t()

Выполняет обход выражений в кавычках по принципу «обхода в глубину», «префиксного обхода».

prewalk(ast, acc, fun)

prewalk(t(), any(), (t(), any() -> {t(), any()})) ::
  {t(), any()}

Выполняет обход выражений в кавычках по принципу «обхода в глубину», «префиксного обхода» с использованием аккумулятора.

quoted_literal?(term) (с версии 1.7.0)

quoted_literal?(expr()) :: false
quoted_literal?(literal()) :: true

Возвращает true если данное выражение в кавычках является литералом AST.

special_form?(name, arity) (с версии 1.7.0)

special_form?(name :: atom(), arity()) :: boolean()

Возвращает true если данное имя и арность являются специальной формой.

to_string(tree, fun \\ fn _ast, string -> string end)

to_string(Macro.t(), (Macro.t(), String.t() -> String.t())) :: String.t()

Преобразует AST заданного выражения в строку.

Указанная fun вызывается для каждого узла в AST с двумя аргументами: AST узла, который печатается, и строковое представление этого же узла. Значение возвращаемое этой функцией, используется в качестве конечного строкового представления для этого узла AST.

Эта функция отбрасывает всю форматирование исходного кода.

Примеры

iex> Macro.to_string(quote(do: foo.bar(1, 2, 3)))
"foo.bar(1, 2, 3)"

iex> Macro.to_string(quote(do: 1 + 2), fn
...>   1, _string -> "one"
...>   2, _string -> "two"
...>   _ast, string -> string
...> end)
"one + two"

traverse(ast, acc, pre, post)

traverse(
  t(),
  any(),
  (t(), any() -> {t(), any()}),
  (t(), any() -> {t(), any()})
) :: {t(), any()}

Выполняет обход выражений в кавычках по принципу «обхода в глубину» с использованием аккумулятора.

underscore(atom)

Преобразует данный атом или бинарный формат в нижнее подчеркивание.

Если задан атом, предполагается, что это модуль Elixir, поэтому он преобразуется в бинарный и затем обрабатывается.

Эта функция была разработана для преобразования идентификаторов/токенов языка в нижнее подчеркивание, поэтому она принадлежит модулю Macro. Не используйте её как общий механизм для преобразования строк в нижнее подчеркивание, так как она не поддерживает Unicode или символы, которые не являются допустимыми в идентификаторах Elixir.

Примеры

iex> Macro.underscore("FooBar")
"foo_bar"

iex> Macro.underscore("Foo.Bar")
"foo/bar"

iex> Macro.underscore(Foo.Bar)
"foo/bar"

В общем случае, underscore можно рассматривать как обратную функцию camelize, однако в некоторых случаях форматирование может быть потеряно:

iex> Macro.underscore("SAPExample")
"sap_example"

iex> Macro.camelize("sap_example")
"SapExample"

iex> Macro.camelize("hello_10")
"Hello10"

unescape_string(chars)

unescape_string(String.t()) :: String.t()

Разворачивает данные chars.

Это поведение разворачивания используется по умолчанию в Elixir строках в одинарных и двойных кавычках. См. unescape_string/2 для информации о том, как настроить карту экранирования.

В этой настройке Elixir экранирует следующее: \0, \a, \b, \d, \e, \f, \n, \r, \s, \t и \v. Байты могут быть заданы в шестнадцатеричном формате через \xNN, а кодовые точки Unicode через \uNNNN экранирование.

Эта функция обычно используется в реализациях сигилов (например, ~r, ~s и др.), которые получают сырую, необработанную строку.

Примеры

iex> Macro.unescape_string("example\\n")
"example\n"

В примере выше, мы передаём строку с \n экранированной, и получаем версию без неё.

unescape_string(chars, map)

unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) ::
  String.t()

Разворачивает данные chars в соответствии с данной картой.

См. unescape_string/1, если вы хотите использовать ту же карту, что и в Elixir строках в одинарных и двойных кавычках.

Карта

Карта должна быть функцией. Функция получает целое число, представляющее кодовую точку символа, который она должна развернуть. Вот функция отображения по умолчанию, реализованная в Elixir:

def unescape_map(unicode), do: true
def unescape_map(hex), do: true
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
def unescape_map(?e), do: ?\e
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?s), do: ?\s
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(e),  do: e

Если функция unescape_map/1 возвращает false, символ не экранируется, и обратная косая черта сохраняется в строке.

Шестнадцатеричные и кодовые точки Unicode будут экранированы, если функция карты возвращает true для ?x. Кодовые точки Unicode, если функция карты возвращает true для ?u.

Примеры

Использование функции unescape_map/1 определённой выше, очень просто:

Macro.unescape_string "example\\n", &unescape_map(&1)

unpipe(expr)

unpipe(Macro.t()) :: [Macro.t()]

Разбивает выражение pipeline на список.

AST для pipeline (последовательность применений |>) похож на AST последовательности бинарных операторов или применений функций: выражение верхнего уровня — это правое выражение :|> (которое выполняется последним), а его левая и правая части — это его аргументы:

quote do: 100 |> div(5) |> div(2)
#=> {:|>, _, [arg1, arg2]}

В примере выше, |> pipe — это правое pipe; arg1 — это AST для 100 |> div(5), а arg2 — это AST для div(2).

Часто бывает полезно иметь AST для такого pipeline как список применений функций. Эта функция делает именно это:

Macro.unpipe(quote do: 100 |> div(5) |> div(2))
#=> [{100, 0}, {{:div, [], [5]}, 0}, {{:div, [], [2]}, 0}]

Мы получаем список, который следует непосредственно за pipeline: сначала 100, затем div(5) (точнее, его AST), затем div(2). 0 в качестве второго элемента кортежа — это позиция предыдущего элемента в pipeline внутри текущего применения функции: {{:div, [], [5]}, 0} означает, что предыдущий элемент (100) будет вставлен в качестве 0-го (первого) аргумента функции div/2, так что AST для этой функции станет {:div, [], [100, 5]} (div(100, 5)).

update_meta(quoted, fun)

update_meta(t(), (keyword() -> keyword())) :: t()

Применяет заданную функцию к метаданным узла, если они есть.

Это часто полезно при использовании с Macro.prewalk/2 для удаления информации, такой как строки и счётчики гигиены из выражения для хранения или сравнения.

Примеры

iex> quoted = quote line: 10, do: sample()
{:sample, [line: 10], []}
iex> Macro.update_meta(quoted, &Keyword.delete(&1, :line))
{:sample, [], []}

validate(expr)

validate(term()) :: :ok | {:error, term()}

Проверяет, являются ли заданные выражения допустимыми выражениями в кавычках.

Проверяет Macro.t/0 для спецификации допустимого выражения в кавычках.

Возвращает :ok если выражение допустимо. В противном случае возвращает кортеж в форме {:error, remainder}, где remainder — недопустимая часть выражения в кавычках.

Примеры

iex> Macro.validate({:two_element, :tuple})
:ok
iex> Macro.validate({:three, :element, :tuple})
{:error, {:three, :element, :tuple}}

iex> Macro.validate([1, 2, 3])
:ok
iex> Macro.validate([1, 2, 3, {4}])
{:error, {4}}

var(var, context)

var(var, context) :: {var, [], context} when var: atom(), context: atom()

Генерирует узел AST, представляющий переменную, заданную атомами var и context.

Примеры

Для построения переменной требуется контекст. В большинстве случаев, для сохранения гигиены, контекст должен быть __MODULE__/0:

iex> Macro.var(:foo, __MODULE__)
{:foo, [], __MODULE__}

Однако, если необходимо получить доступ к переменной пользователя, можно использовать nil:

iex> Macro.var(:foo, nil)
{:foo, [], nil}

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.7.4/Macro.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API