Spec-Zone.ru › Elixir 1.4

Макросы

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

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

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

Дерево абстрактного синтаксиса (AST)

Функции

camelize(string)

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

decompose_call(ast)

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

escape(expr, opts \\ [])

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

expand(tree, env)

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

expand_once(ast, env)

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

pipe(expr, call_args, position)

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

postwalk(ast, fun)

Выполняет обход дерева ссылок в постфиксной форме (обход в глубину)

postwalk(ast, acc, fun)

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

prewalk(ast, fun)

Выполняет обход дерева ссылок в префиксной форме (обход в глубину)

prewalk(ast, acc, fun)

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

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

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

traverse(ast, acc, pre, post)

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

underscore(atom)

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

unescape_string(chars)

Раскрывает эскэпированные символы из строки

unescape_string(chars, map)

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

unescape_tokens(tokens)

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

unescape_tokens(tokens, map)

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

unpipe(expr)

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

update_meta(quoted, fun)

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

validate(expr)

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

var(var, context)

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

Типы

expr()

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

t()

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

Дерево абстрактного синтаксиса (AST)

Функции

camelize(string)

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

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

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

Примеры

iex> Macro.camelize "foo_bar"
"FooBar"

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.t()) :: 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

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()}

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

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

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

Преобразует данное выражение в двоичное представление.

Переданная 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()

Разворачивает заданные символы.

Это поведение разворачивания, используемое по умолчанию в строках 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()

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

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

Карта

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

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(?x), do: true
def unescape_map(?u), do: true
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)

unescape_tokens(tokens)

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

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

Проверьте unescape_string/1 и unescape_string/2 для получения дополнительной информации о разворачивании.

Только токены, которые являются двоичными, разворачиваются, все остальные игнорируются. Эта функция полезна при реализации собственных сигилов. Проверьте реализацию Kernel.sigil_s/2 для примеров.

unescape_tokens(tokens, map)

unescape_tokens([Macro.t()], (non_neg_integer() -> non_neg_integer() | false)) :: [Macro.t()]

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

Проверьте unescape_tokens/1 и unescape_string/2 для получения дополнительной информации.

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.t() -> Keyword.t())) :: 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.4.5/Macro.html

Spec-Zone.ru

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