View Source Macro (Elixir v1.15.5)
Functions for manipulating AST and implementing macros.
Macros are compile-time constructs that receive Elixir's AST as input and return Elixir's AST as output.
Many of the functions in this module exist precisely to work with Elixir AST, to traverse, query, and transform it.
Let's see a simple example that shows the difference between functions and macros:
defmodule Example do
defmacro macro_inspect(value) do
IO.inspect(value)
value
end
def fun_inspect(value) do
IO.inspect(value)
value
end
end
Now let's give it a try:
import Example
macro_inspect(1)
#=> 1
#=> 1
fun_inspect(1)
#=> 1
#=> 1
So far they behave the same, as we are passing an integer as argument. But let's see what happens when we pass an expression:
macro_inspect(1 + 2)
#=> {:+, [line: 3], [1, 2]}
#=> 3
fun_inspect(1 + 2)
#=> 3
#=> 3
The macro receives the representation of the code given as argument,
while a function receives the result of the code given as argument.
A macro must return a superset of the code representation. See
input/0
and output/0
for more information.
To learn more about Elixir's AST and how to build them programmatically,
see quote/2
.
Evaluating code
The functions in this module do not evaluate code. In fact, evaluating code from macros is often an anti-pattern. For code evaluation, see the
Code
module.
Custom Sigils
Macros are also commonly used to implement custom sigils.
Sigils start with ~
and are followed by one lowercase letter or by one
or more uppercase letters, and then a separator
(see the Syntax Reference). One example is
~D[2020-10-13]
to define a date.
To create a custom sigil, define a macro 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 one or more upper case letters (such as sigil_X
and
sigil_EXAMPLE
) then the string will not be interpolated.
Valid modifiers are ASCII letters and digits. Any other character will cause a syntax error.
Single-letter sigils are typically reserved to the language. Multi-letter sigils are uppercased and extensively used by the community to embed alternative markups and data-types within Elixir source code.
The module containing the custom sigil must be imported before the sigil syntax can be used.
Examples
As an example, let's define a sigil ~x
and sigil ~X
which
return its contents as a string. However, if the r
modifier
is given, it reverses the string instead:
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
A captured remote function in the format of &Mod.fun/arity
The inputs of a macro
A keyword list of AST metadata.
The output of a macro
Abstract Syntax Tree (AST)
Functions
Converts the given string to CamelCase format.
Classifies a runtime atom
based on its possible AST placement.
Default backend for Kernel.dbg/2
.
Decomposes a local or remote call into its remote part (when provided), function name and argument list.
Recursively escapes a value so it can be inserted into a syntax tree.
Receives an AST node and expands it until it can no longer be expanded.
Expands all literals in ast
with the given env
.
Expands all literals in ast
with the given acc
and fun
.
Receives an AST node and expands it once.
Generates AST nodes for a given number of required argument
variables using Macro.var/2
.
Generates AST nodes for a given number of required argument
variables using Macro.unique_var/2
.
Inspects atom
according to different source formats.
Returns true
if the given name and arity is an operator.
Returns the path to the node in ast
which fun
returns true.
Pipes expr
into the call_args
at the given position
.
This function behaves like prewalk/2
, but performs a depth-first,
post-order traversal of quoted expressions.
This functions behaves like prewalk/3
, but performs a depth-first,
post-order traversal of quoted expressions using an accumulator.
Returns an enumerable that traverses the ast
in depth-first,
post-order traversal.
Performs a depth-first, pre-order traversal of quoted expressions.
Performs a depth-first, pre-order traversal of quoted expressions using an accumulator.
Returns an enumerable that traverses the ast
in depth-first,
pre-order traversal.
Returns true
if the given quoted expression represents a quoted literal.
Returns true
if the given name and arity is a special form.
Expands the struct given by module
in the given env
.
Converts the given expression AST to a string.
Converts the given expression AST to a string.
Performs a depth-first traversal of quoted expressions using an accumulator.
Converts the given argument to a string with the underscore-slash format.
Unescapes characters in a string.
Unescapes characters in a string according to the given mapping.
Generates an AST node representing a unique variable
given by the atoms var
and context
.
Breaks a pipeline expression into a list.
Applies the given function to the node metadata if it contains one.
Validates the given expressions are valid quoted expressions.
Generates an AST node representing the variable given
by the atoms var
and context
.
Types
@type captured_remote_function() :: (... -> any())
A captured remote function in the format of &Mod.fun/arity
The inputs of a macro
@type metadata() :: keyword()
A keyword list of AST metadata.
The metadata in Elixir AST is a keyword list of values. Any key can be used
and different parts of the compiler may use different keys. For example,
the AST received by a macro will always include the :line
annotation,
while the AST emitted by quote/2
will only have the :line
annotation if
the :line
option is provided.
The following metadata keys are public:
:context
- Defines the context in which the AST was generated. For example,quote/2
will include the module callingquote/2
as the context. This is often used to distinguish regular code from code generated by a macro or byquote/2
.:counter
- The variable counter used for variable hygiene. In terms of the compiler, each variable is identified by the combination of eithername
andmetadata[:counter]
, orname
andcontext
.:generated
- Whether the code should be considered as generated by the compiler or not. This means the compiler and tools like Dialyzer may not emit certain warnings.:if_undefined
- How to expand a variable that is undefined. Set it to:apply
if you want a variable to become a nullary call without warning or:raise
:keep
- Used byquote/2
with the optionlocation: :keep
to annotate the file and the line number of the quoted source.:line
- The line number of the AST node.:from_brackets
- Used to determine whether a call toAccess.get/3
is from bracket syntax or a function call.
The following metadata keys are enabled by Code.string_to_quoted/2
:
:closing
- contains metadata about the closing pair, such as a}
in a tuple or in a map, or such as the closing)
in a function call with parens. The:closing
does not delimit the end of expression if there are:do
and:end
metadata (when:token_metadata
is true):column
- the column number of the AST node (when:columns
is true):delimiter
- contains the opening delimiter for sigils, strings, and charlists as a string (such as"{"
,"/"
,"'"
, and the like):format
- set to:keyword
when an atom is defined as a keyword:do
- contains metadata about thedo
location in a function call withdo
-end
blocks (when:token_metadata
is true):end
- contains metadata about theend
location in a function call withdo
-end
blocks (when:token_metadata
is true):end_of_expression
- denotes when the end of expression effectively happens. Available for all expressions except the last one inside a__block__
(when:token_metadata
is true):indentation
- indentation of a sigil heredoc
The following metadata keys are private:
:alias
- Used for alias hygiene.:ambiguous_op
- Used for improved error messages in the compiler.:imports
- Used for import hygiene.:var
- Used for improved error messages on undefined variables.
Do not rely on them as they may change or be fully removed in future versions
of the language. They are often used by quote/2
and the compiler to provide
features like hygiene, better error messages, and so forth.
If you introduce custom keys into the AST metadata, please make sure to prefix them with the name of your library or application, so that they will not conflict with keys that could potentially be introduced by the compiler in the future.
@type output() :: output_expr() | {output(), output()} | [output()] | atom() | number() | binary() | captured_remote_function() | pid()
The output of a macro
@type t() :: input()
Abstract Syntax Tree (AST)
Functions
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"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
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"
@spec classify_atom(atom()) :: :alias | :identifier | :quoted | :unquoted
Classifies a runtime atom
based on its possible AST placement.
It returns one of the following atoms:
:alias
- the atom represents an alias:identifier
- the atom can be used as a variable or local function call (as well as be an unquoted atom):unquoted
- the atom can be used in its unquoted form, includes operators and atoms with@
in them:quoted
- all other atoms which can only be used in their quoted form
Most operators are going to be :unquoted
, such as :+
, with
some exceptions returning :quoted
due to ambiguity, such as
:"::"
. Use operator?/2
to check if a given atom is an operator.
Examples
iex> Macro.classify_atom(:foo)
:identifier
iex> Macro.classify_atom(Foo)
:alias
iex> Macro.classify_atom(:foo@bar)
:unquoted
iex> Macro.classify_atom(:+)
:unquoted
iex> Macro.classify_atom(:Foo)
:unquoted
iex> Macro.classify_atom(:"with spaces")
:quoted
@spec dbg(t(), t(), Macro.Env.t()) :: t()
Default backend for Kernel.dbg/2
.
This function provides a default backend for Kernel.dbg/2
. See the
Kernel.dbg/2
documentation for more information.
This function:
- prints information about the given
env
- prints information about
code
and its returned value (usingopts
to inspect terms) - returns the value returned by evaluating
code
You can call this function directly to build Kernel.dbg/2
backends that fall back
to this function.
This function raises if the context of the given env
is :match
or :guard
.
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
iex> Macro.decompose_call(quote(do: {:foo, [], []}))
:error
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 leavesunquote/1
andunquote_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. 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 bytecode, 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 dataExUnit
needs to keep around. Only the minimal amount of metadata is kept, such as:line
and:no_parens
.
Comparison to quote/2
The escape/2
function is sometimes confused with 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 quote/2
produces syntax trees for
expressions.
@spec expand(input(), Macro.Env.t()) :: output()
Receives an AST node and expands it until it can no longer be expanded.
Note this function does not traverse the AST, only the root node is expanded.
This function uses expand_once/2
under the hood. Check
it out for more information and examples.
@spec expand_literals(input(), Macro.Env.t()) :: output()
Expands all literals in ast
with the given env
.
This function is mostly used to remove compile-time dependencies from AST nodes. In such cases, the given environment is usually manipulated to represent a function:
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
At the moment, the only expandable literal nodes in an AST are aliases, so this function only expands aliases (and it does so anywhere in a literal).
However, be careful when removing compile-time dependencies between modules. If you remove them but you still invoke the module at compile-time, Elixir will be unable to properly recompile modules when they change.
Expands all literals in ast
with the given acc
and fun
.
fun
will be invoked with an expandable AST node and acc
and
must return a new node with acc
. This is a general version of
expand_literals/2
which supports a custom expansion function.
Please check expand_literals/2
for use cases and pitfalls.
@spec expand_once(input(), Macro.Env.t()) :: output()
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
- Compilation environment macros (
__CALLER__/0
,__DIR__/0
,__ENV__/0
and__MODULE__/0
) - Module attributes reader (
@foo
)
If the expression cannot be expanded, it returns the expression itself. This function does not traverse the AST, only the root node is expanded.
expand_once/2
performs the expansion just once. Check expand/2
to perform 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
@spec generate_arguments(0, context :: atom()) :: []
@spec generate_arguments(pos_integer(), context) :: [{atom(), [], context}, ...] when context: atom()
Generates AST nodes for a given number of required argument
variables using Macro.var/2
.
Note the arguments are not unique. If you later on want
to access the same variables, you can invoke this function
with the same inputs. Use generate_unique_arguments/2
to
generate a unique arguments that can't be overridden.
Examples
iex> Macro.generate_arguments(2, __MODULE__)
[{:arg1, [], __MODULE__}, {:arg2, [], __MODULE__}]
@spec generate_unique_arguments(0, context :: atom()) :: []
@spec generate_unique_arguments(pos_integer(), context) :: [ {atom(), [{:counter, integer()}], context}, ... ] when context: atom()
Generates AST nodes for a given number of required argument
variables using Macro.unique_var/2
.
Examples
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
iex> {:arg1, [counter: c1], __MODULE__} = var1
iex> {:arg2, [counter: c2], __MODULE__} = var2
iex> is_integer(c1) and is_integer(c2)
true
Inspects atom
according to different source formats.
The atom can be inspected according to the three different
formats it appears in the AST: as a literal (:literal
),
as a key (:key
), or as the function name of a remote call
(:remote_call
).
Examples
As a literal
Literals include regular atoms, quoted atoms, operators,
aliases, and the special nil
, true
, and false
atoms.
iex> Macro.inspect_atom(:literal, nil)
"nil"
iex> Macro.inspect_atom(:literal, :foo)
":foo"
iex> Macro.inspect_atom(:literal, :<>)
":<>"
iex> Macro.inspect_atom(:literal, :Foo)
":Foo"
iex> Macro.inspect_atom(:literal, Foo.Bar)
"Foo.Bar"
iex> Macro.inspect_atom(:literal, :"with spaces")
":\"with spaces\""
As a key
Inspect an atom as a key of a keyword list or a map.
iex> Macro.inspect_atom(:key, :foo)
"foo:"
iex> Macro.inspect_atom(:key, :<>)
"<>:"
iex> Macro.inspect_atom(:key, :Foo)
"Foo:"
iex> Macro.inspect_atom(:key, :"with spaces")
"\"with spaces\":"
As a remote call
Inspect an atom the function name of a remote call.
iex> Macro.inspect_atom(:remote_call, :foo)
"foo"
iex> Macro.inspect_atom(:remote_call, :<>)
"<>"
iex> Macro.inspect_atom(:remote_call, :Foo)
"\"Foo\""
iex> Macro.inspect_atom(:remote_call, :"with spaces")
"\"with spaces\""
Returns true
if the given name and arity is an operator.
Examples
iex> Macro.operator?(:not_an_operator, 3)
false
iex> Macro.operator?(:.., 0)
true
iex> Macro.operator?(:+, 1)
true
iex> Macro.operator?(:++, 2)
true
iex> Macro.operator?(:..//, 3)
true
Returns the path to the node in ast
which fun
returns true.
The path is a list, starting with the node in which fun
returns
true, followed by all of its parents.
Computing the path can be an efficient operation when you want to find a particular node in the AST within its context and then assert something about it.
Examples
iex> Macro.path(quote(do: [1, 2, 3]), & &1 == 3)
[3, [1, 2, 3]]
iex> Macro.path(quote(do: Foo.bar(3)), & &1 == 3)
[3, quote(do: Foo.bar(3))]
iex> Macro.path(quote(do: %{foo: [bar: :baz]}), & &1 == :baz)
[
:baz,
{:bar, :baz},
[bar: :baz],
{:foo, [bar: :baz]},
{:%{}, [], [foo: [bar: :baz]]}
]
Pipes expr
into the call_args
at the given position
.
This function can be used to implement |>
like functionality. For example,
|>
itself is implemented as:
defmacro left |> right do
Macro.pipe(left, right, 0)
end
expr
is the AST of an expression. call_args
must be the AST of a call,
otherwise this function will raise an error. As an example, consider the pipe
operator |>/2
, which uses this function to build pipelines.
Even if the expression is piped into the AST, it doesn't necessarily mean that
the AST is valid. For example, you could pipe an argument to div/2
, effectively
turning it into a call to div/3
, which is a function that doesn't exist by
default. The code will raise unless a div/3
function is locally defined.
This function behaves like prewalk/2
, but performs a depth-first,
post-order traversal of quoted expressions.
This functions behaves like prewalk/3
, but performs a depth-first,
post-order traversal of quoted expressions using an accumulator.
@spec postwalker(t()) :: Enumerable.t()
Returns an enumerable that traverses the ast
in depth-first,
post-order traversal.
Examples
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.postwalker(ast), & &1)
[1, "abc", {:foo, [], [1, "abc"]}]
Performs a depth-first, pre-order traversal of quoted expressions.
Returns a new AST where each node is the result of invoking fun
on each
corresponding node of ast
.
Examples
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> new_ast = Macro.prewalk(ast, fn
...> {:+, meta, children} -> {:*, meta, children}
...> {:*, meta, children} -> {:+, meta, children}
...> other -> other
...> end)
iex> {:*, _, [5, {:+, _, [3, 7]}]} = new_ast
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []}
Performs a depth-first, pre-order traversal of quoted expressions using an accumulator.
Returns a tuple where the first element is a new AST where each node is the
result of invoking fun
on each corresponding node and the second one is the
final accumulator.
Examples
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} = Macro.prewalk(ast, [], fn
...> {:+, meta, children}, acc -> {{:*, meta, children}, [:+ | acc]}
...> {:*, meta, children}, acc -> {{:+, meta, children}, [:* | acc]}
...> other, acc -> {other, acc}
...> end)
iex> {{:*, _, [5, {:+, _, [3, 7]}]}, [:*, :+]} = {new_ast, acc}
iex> Code.eval_quoted(ast)
{26, []}
iex> Code.eval_quoted(new_ast)
{50, []}
@spec prewalker(t()) :: Enumerable.t()
Returns an enumerable that traverses the ast
in depth-first,
pre-order traversal.
Examples
iex> ast = quote do: foo(1, "abc")
iex> Enum.map(Macro.prewalker(ast), & &1)
[{:foo, [], [1, "abc"]}, 1, "abc"]
Returns true
if the given quoted expression represents a quoted literal.
Atoms and numbers are always literals. Binaries, lists, tuples, maps, and structs are only literals if all of their terms are also literals.
Examples
iex> Macro.quoted_literal?(quote(do: "foo"))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1}))
true
iex> Macro.quoted_literal?(quote(do: {"foo", 1, :baz}))
true
iex> Macro.quoted_literal?(quote(do: %{foo: "bar"}))
true
iex> Macro.quoted_literal?(quote(do: %URI{path: "/"}))
true
iex> Macro.quoted_literal?(quote(do: URI.parse("/")))
false
iex> Macro.quoted_literal?(quote(do: {foo, var}))
false
Returns true
if the given name and arity is a special form.
@spec struct!(module, Macro.Env.t()) :: %{ :__struct__ => module, optional(atom()) => any() } when module: module()
Expands the struct given by module
in the given env
.
This is useful when a struct needs to be expanded at compilation time and the struct being expanded may or may not have been compiled. This function is also capable of expanding structs defined under the module being compiled.
It will raise CompileError
if the struct is not available.
From Elixir v1.12, calling this function also adds an export
dependency on the given struct.
Converts the given expression AST to a string.
This is a convenience function for converting AST into
a string, which discards all formatting of the original
code and wraps newlines around 98 characters. See
Code.quoted_to_algebra/2
as a lower level function
with more control around formatting.
Examples
iex> Macro.to_string(quote(do: foo.bar(1, 2, 3)))
"foo.bar(1, 2, 3)"
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
Macro.to_string(quote(do: 1 + 2), fn
1, _string -> "one"
2, _string -> "two"
_ast, string -> string
end)
#=> "one + two"
@spec traverse(t(), any(), (t(), any() -> {t(), any()}), (t(), any() -> {t(), any()})) :: {t(), any()}
Performs a depth-first traversal of quoted expressions using an accumulator.
Returns a tuple where the first element is a new AST and the second one is
the final accumulator. The new AST is the result of invoking pre
on each
node of ast
during the pre-order phase and post
during the post-order
phase.
Examples
iex> ast = quote do: 5 + 3 * 7
iex> {:+, _, [5, {:*, _, [3, 7]}]} = ast
iex> {new_ast, acc} =
...> Macro.traverse(
...> ast,
...> [],
...> fn
...> {:+, meta, children}, acc -> {{:-, meta, children}, [:- | acc]}
...> {:*, meta, children}, acc -> {{:/, meta, children}, [:/ | acc]}
...> other, acc -> {other, acc}
...> end,
...> fn
...> {:-, meta, children}, acc -> {{:min, meta, children}, [:min | acc]}
...> {:/, meta, children}, acc -> {{:max, meta, children}, [:max | acc]}
...> other, acc -> {other, acc}
...> end
...> )
iex> {:min, _, [5, {:max, _, [3, 7]}]} = new_ast
iex> [:min, :max, :/, :-] = acc
iex> Code.eval_quoted(new_ast)
{5, []}
Converts the given argument to a string with the underscore-slash format.
The argument must either be an atom or a string. If an atom is given, it is assumed to be an Elixir module, so it is converted to a string and then processed.
This function was designed to format language identifiers/tokens with the underscore-slash format,
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"
iex> Macro.camelize("foo/bar")
"Foo.Bar"
Unescapes characters in a string.
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 code points as
\uNNNN
escapes.
This function is commonly used on sigil implementations
(like ~r
, ~s
and others), which receive a raw, unescaped
string, and it can be used anywhere that needs to mimic how
Elixir parses strings.
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.
@spec unescape_string(String.t(), (non_neg_integer() -> non_neg_integer() | false)) :: String.t()
Unescapes characters in a string according to the given mapping.
Check unescape_string/1
if you want to use the same mapping
as Elixir single- and double-quoted strings.
Mapping function
The mapping function receives an integer representing the code point
of the character it wants to unescape. There are also the special atoms
:newline
, :unicode
, and :hex
, which control newline, unicode,
and escaping respectively.
Here is the default mapping function implemented by Elixir:
def unescape_map(:newline), do: true
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.
Examples
Using the unescape_map/1
function defined above is easy:
Macro.unescape_string("example\\n", &unescape_map(&1))
@spec unique_var(var, context) :: {var, [{:counter, integer()}], context} when var: atom(), context: atom()
Generates an AST node representing a unique variable
given by the atoms var
and context
.
Calling this function with the same arguments will
generate another variable, with its own unique counter.
See var/2
for an alternative.
Examples
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
iex> is_integer(c)
true
Breaks a pipeline expression into a list.
The AST for a pipeline (a sequence of applications of |>/2
) 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 |>/2
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)
).
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, [], []}
Validates the given expressions are valid quoted expressions.
Check the type Macro.t/0
for a complete 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}}
Generates an AST node representing the variable given
by the atoms var
and context
.
Note this variable is not unique. If you later on want
to access this same variable, you can invoke var/2
again with the same arguments. Use unique_var/2
to
generate a unique variable that can't be overridden.
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}