Macro

Conveniences for working with macros.

Custom Sigils

To create a custom sigil, define a function with the name sigil_{identifier} that takes two arguments. The first argument will be the string, the second will be a charlist containing any modifiers. If the sigil is lower case (such as sigil_x) then the string argument will allow interpolation. If the sigil is upper case (such as sigil_X) then the string will not be interpolated.

Valid modifiers include only lower and upper case letters. Other characters will cause a syntax error.

The module containing the custom sigil must be imported before the sigil syntax can be used.

Examples

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"

Summary

Types

expr()

Represents expressions in the AST

literal()

Represents literals in the AST

t()

Abstract Syntax Tree (AST)

Functions

camelize(string)

Converts the given string to CamelCase format

decompose_call(ast)

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

escape(expr, opts \\ [])

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

expand(tree, env)

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

expand_once(ast, env)

Receives an AST node and expands it once

generate_arguments(amount, context)

Generates AST nodes for a given number of required argument variables using Macro.var/2

operator?(name, arity)

Returns true if the given name and arity is an operator

pipe(expr, call_args, position)

Pipes expr into the call_args at the given position

postwalk(ast, fun)

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

postwalk(ast, acc, fun)

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

prewalk(ast, fun)

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

prewalk(ast, acc, fun)

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

quoted_literal?(term)

Returns true if the given quoted expression is an AST literal

special_form?(name, arity)

Returns true if the given name and arity is a special form

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

Converts the given expression AST to a string

traverse(ast, acc, pre, post)

Performs a depth-first traversal of quoted expressions using an accumulator

underscore(atom)

Converts the given atom or binary to underscore format

unescape_string(chars)

Unescapes the given chars

unescape_string(chars, map)

Unescapes the given chars according to the map given

unpipe(expr)

Breaks a pipeline expression into a list

update_meta(quoted, fun)

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

validate(expr)

Validates the given expressions are valid quoted expressions

var(var, context)

Generates an AST node representing the variable given by the atoms var and context

Types

expr()

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

Represents expressions in the AST

literal()

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

Represents literals in the AST

t()

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

Abstract Syntax Tree (AST)

Functions

camelize(string)

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

Converts the given string to CamelCase format.

This function was designed to camelize language identifiers/tokens, that’s why it belongs to the Macro module. Do not use it as a general mechanism for camelizing strings as it does not support Unicode or characters that are not valid in Elixir identifiers.

Examples

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

If uppercase characters are present, they are not modified in any way as a mechanism to preserve acronyms:

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

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

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

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

Examples

iex> Macro.escape(:foo)
:foo

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

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

Options

  • :unquote - when true, this function leaves unquote/1 and unquote_splicing/1 statements unescaped, effectively unquoting the contents on escape. This option is useful only when escaping ASTs which may have quoted fragments in them. Defaults to false.

  • :prune_metadata - when true, removes metadata from escaped AST nodes. Note this option changes the semantics of escaped code and it should only be used when escaping ASTs, never values. Defaults to false.

    As an example, ExUnit stores the AST of every assertion, so when an assertion fails we can show code snippets to users. Without this option, each time the test module is compiled, we get a different MD5 of the module byte code, because the AST contains metadata, such as counters, specific to the compilation environment. By pruning the metadata, we ensure that the module is deterministic and reduce the amount of data ExUnit needs to keep around.

Comparison to Kernel.quote/2

The escape/2 function is sometimes confused with Kernel.SpecialForms.quote/2, because the above examples behave the same with both. The key difference is best illustrated when the value to escape is stored in a variable.

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 is used to escape values (either directly passed or variable bound), while Kernel.SpecialForms.quote/2 produces syntax trees for expressions.

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 it out for more information and examples.

expand_once(ast, env)

Receives an AST node and expands it once.

The following contents are expanded:

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_charlist(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 follows:

{:__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 straightforward 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_charlist(expanded))

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

generate_arguments(amount, context) (since 1.5.0)

Generates AST nodes for a given number of required argument variables using Macro.var/2.

Examples

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

operator?(name, arity) (since 1.7.0)

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

Returns true if the given name and arity is an operator.

pipe(expr, call_args, position)

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

Pipes expr into the call_args at the given position.

postwalk(ast, fun)

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

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

postwalk(ast, acc, fun)

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)

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

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

prewalk(ast, acc, fun)

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

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

quoted_literal?(term) (since 1.7.0)

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

Returns true if the given quoted expression is an AST literal.

special_form?(name, arity) (since 1.7.0)

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

Returns true if the given name and arity is a special form.

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

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

Converts the given expression AST to a string.

The given fun is called for every node in the AST with two arguments: the AST of the node being printed and the string representation of that same node. The return value of this function is used as the final string representation for that AST node.

This function discards all formatting of the original code.

Examples

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

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

underscore(atom)

Converts the given atom or binary to underscore format.

If an atom is given, it is assumed to be an Elixir module, so it is converted to a binary and then processed.

This function was designed to underscore language identifiers/tokens, that’s why it belongs to the Macro module. Do not use it as a general mechanism for underscoring strings as it does not support Unicode or characters that are not valid in Elixir identifiers.

Examples

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

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

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

In general, underscore can be thought of as the reverse of camelize, however, in some cases formatting may be lost:

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

Unescapes 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. Bytes can be given as hexadecimals via \xNN and Unicode Codepoints as \uNNNN escapes.

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)

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

Unescapes 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(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

If the unescape_map/1 function returns false, the char is not escaped and the backslash is kept in the string.

Hexadecimals and Unicode codepoints will be escaped if the map function returns true for ?x. Unicode codepoints if the map function returns true for ?u.

Examples

Using the unescape_map/1 function defined above is easy:

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

unpipe(expr)

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

Breaks a pipeline expression into a list.

The AST for a pipeline (a sequence of applications of |>) is similar to the AST of a sequence of binary operators or function applications: the top-level expression is the right-most :|> (which is the last one to be executed), and its left-hand and right-hand sides are its arguments:

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

In the example above, the |> pipe is the right-most pipe; arg1 is the AST for 100 |> div(5), and arg2 is the AST for div(2).

It’s often useful to have the AST for such a pipeline as a list of function applications. This function does exactly that:

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

We get a list that follows the pipeline directly: first the 100, then the div(5) (more precisely, its AST), then div(2). The 0 as the second element of the tuples is the position of the previous element in the pipeline inside the current function application: {{:div, [], [5]}, 0} means that the previous element (100) will be inserted as the 0th (first) argument to the div/2 function, so that the AST for that function will become {:div, [], [100, 5]} (div(100, 5)).

update_meta(quoted, fun)

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

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

This is often useful when used with Macro.prewalk/2 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)

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

Validates the given expressions are valid quoted expressions.

Checks the Macro.t/0 for the specification of a valid quoted expression.

It returns :ok if the expression is valid. Otherwise it returns a tuple in the form of {:error, remainder} where remainder is the invalid part of the quoted expression.

Examples

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

Generates an 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__/0:

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}

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