Macro

Introduction #

Conveniences for working with macros.

Source

Types #

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

Abstract Syntax Tree (AST)

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

Expr node (remaining ones are literals)

Functions #

decompose_call(arg1)

Specs

Decomposes a local or remote call into its remote part (when provided), function name and argument list.

Returns :error when an invalid call syntax is provided.

Examples

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 \\ [])

Specs

Recursively escapes a value so it can be inserted into a syntax tree.

One may pass unquote: true to escape/2 which leaves unquote statements unescaped, effectively unquoting the contents on escape.

Examples

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)

Receives an AST node and expands it until it can no longer be expanded.

This function uses expand_once/2 under the hood. Check expand_once/2 for more information and examples.

expand_once(ast, env)

Receives an AST node and expands it once.

The following contents are expanded:

  • Macros (local or remote)
  • Aliases are expanded (if possible) and return atoms
  • Pseudo-variables (__ENV__, __MODULE__ and __DIR__)
  • Module attributes reader (@foo)

If the expression cannot be expanded, it returns the expression itself. Notice that expand_once/2 performs the expansion just once and it is not recursive. Check expand/2 for expansion until the node can no longer be expanded.

Examples

In the example below, we have a macro that generates a module with a function named name_length that returns the length of the module name. The value of this function will be calculated at compilation time and not at runtime.

Consider the implementation below:

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

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

When invoked like this:

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

The compilation will fail because My.Module when quoted is not an atom, but a syntax tree as follow:

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

That said, we need to expand the aliases node above to an atom, so we can retrieve its length. Expanding the node is not straight-forward because we also need to expand the caller aliases. For example:

alias MyHelpers, as: My

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

The final module name will be MyHelpers.Module and not My.Module. With Macro.expand/2, such aliases are taken into consideration. Local and remote macros are also expanded. We could rewrite our macro above to use this function as:

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

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

pipe(expr, call_args, position)

Specs

Pipes expr into the call_args at the given position.

postwalk(ast, fun)

Specs

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

Performs a depth-first, post-order traversal of quoted expressions.

postwalk(ast, acc, fun)

Specs

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

Performs a depth-first, post-order traversal of quoted expressions using an accumulator.

prewalk(ast, fun)

Specs

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

Performs a depth-first, pre-order traversal of quoted expressions.

prewalk(ast, acc, fun)

Specs

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

Performs a depth-first, pre-order traversal of quoted expressions using an accumulator.

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

Specs

Converts the given expression to a binary.

Examples

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

unescape_string(chars)

Specs

Unescape the given chars.

This is the unescaping behaviour used by default in Elixir single- and double-quoted strings. Check unescape_string/2 for information on how to customize the escaping map.

In this setup, Elixir will escape the following: \0, \a, \b, \d, \e, \f, \n, \r, \s, \t and \v. Hexadecimals are also supported via \xNN and \x{NN...} syntax.

This function is commonly used on sigil implementations (like ~r, ~s and others) which receive a raw, unescaped string.

Examples

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

In the example above, we pass a string with \n escaped and return a version with it unescaped.

unescape_string(chars, map)

Specs

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

Unescape the given chars according to the map given.

Check unescape_string/1 if you want to use the same map as Elixir single- and double-quoted strings.

Map

The map must be a function. The function receives an integer representing the codepoint of the character it wants to unescape. Here is the default mapping function implemented by 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(e),  do: e

If the unescape_map function returns false. The char is not escaped and \ is kept in the char list.

Hexadecimals will be escaped if the map function returns true for ?x.

Examples

Using the unescape_map function defined above is easy:

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

unescape_tokens(tokens)

Specs

Unescape the given tokens according to the default map.

Check unescape_string/1 and unescape_string/2 for more information about unescaping.

Only tokens that are binaries are unescaped, all others are ignored. This function is useful when implementing your own sigils. Check the implementation of Kernel.sigil_s/2 for examples.

unescape_tokens(tokens, map)

Specs

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

Unescape the given tokens according to the given map.

Check unescape_tokens/1 and unescape_string/2 for more information.

unpipe(expr)

Specs

Breaks a pipeline expression into a list.

Raises if the pipeline is ill-formed.

update_meta(quoted, fun)

Specs

Applies the given function to the node metadata if it contains one.

This is often useful when used with Macro.prewalk/1 to remove information like lines and hygienic counters from the expression for either storage or comparison.

Examples

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

validate(expr)

Specs

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

Validates the given expressions are valid quoted expressions.

Check the type:Macro.t for the specification of a valid quoted expression.

var(var, context)

Specs

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

Genrates a AST node representing the variable given by the atoms var and context.

Examples

In order to build a variable, a context is expected. Most of the times, in order to preserve hygiene, the context must be __MODULE__:

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

However, if there is a need to access the user variable, nil can be given:

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