Spec-Zone.ru › Elixir 1.6

Макрос

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

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

Чтобы создать пользовательский сигил, определите функцию с именем 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(количество, контекст)

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

pipe(expr, call_args, позиция)

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

postwalk(ast, fun)

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

postwalk(ast, acc, fun)

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

prewalk(ast, fun)

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

prewalk(ast, acc, fun)

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

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(переменная, контекст)

Генерирует узел 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()

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

Можно передать unquote: true в escape/2, что оставляет утверждения unquote/1 не экранированными, эффективно снимая кавычки из содержимого при экранировании.

Примеры

iex> Macro.escape(:foo)
:foo

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

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

expand(tree, env)

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

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

expand_once(ast, env)

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

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

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

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

Примеры

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

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

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

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

Когда вызывается так:

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

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

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

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

alias MyHelpers, as: My

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

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

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

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

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

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

Примеры

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

pipe(expr, call_args, позиция)

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

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

postwalk(ast, fun)

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

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

postwalk(ast, acc, fun)

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

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

prewalk(ast, fun)

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

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

prewalk(ast, acc, fun)

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

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

to_string(дерево, 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()]

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

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

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

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

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

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

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

update_meta(quoted, fun)

update_meta(t(), (keyword() -> 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.6.6/Macro.html

Spec-Zone.ru

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