ExPanda (ExPanda v0.2.0)
View SourceFull macro expansion for Elixir AST introspection.
ExPanda takes Elixir source code (or a pre-parsed AST) and produces an AST
where all macros have been expanded to their underlying forms, while preserving
structural constructs (defmodule, def/defp) as-is.
It uses the Elixir compiler's internal :elixir_expand.expand/3 as the
primary expansion engine, with a fallback to Macro.expand/2 for environments
where the internal API is unavailable.
Quick Start
# Expand a source code string
{:ok, expanded} = ExPanda.expand_string("unless true, do: :never")
# => {:case, _, [true, [do: [...]]]}
# Expand a file
{:ok, expanded} = ExPanda.expand_file("lib/my_module.ex")
# Expand a pre-parsed AST
{:ok, ast} = Code.string_to_quoted("1 |> to_string()")
{:ok, expanded} = ExPanda.expand(ast)
# => {{:., _, [String.Chars, :to_string]}, _, [1]}Structural Preservation
defmodule and def/defp forms are kept intact in the output.
Only their bodies are expanded:
{:ok, expanded} = ExPanda.expand_string("""
defmodule Foo do
def bar(x), do: unless(x, do: :fallback)
end
""")
# defmodule is preserved, but `unless` inside bar's body is expanded to `case`Unexpandable Macros
When a macro cannot be expanded (e.g., the target module is not loaded),
the original node is kept with an @unexpanded error marker prepended:
{:__block__, [], [
{:@, [], [{:unexpanded, [], ["use/1: ..."]}]},
{:use, [], [{:__aliases__, [], [:SomeUnloadedLib]}]}
]}
Summary
Functions
Expand all macros in a pre-parsed Elixir AST.
Expand all macros in a pre-parsed AST with an explicit environment.
Expand all macros in an Elixir source file.
Expand all macros in a source code string.
Expand all macros and return formatted Elixir code.
Functions
Expand all macros in a pre-parsed Elixir AST.
Options
:env- CustomMacro.Envto use as the base environment.:file- File path to set in the environment.
Examples
iex> {:ok, ast} = Code.string_to_quoted("1 |> to_string()")
iex> {:ok, expanded} = ExPanda.expand(ast)
iex> match?({{:., _, [String.Chars, :to_string]}, _, [1]}, expanded)
true
@spec expand(Macro.t(), Macro.Env.t(), keyword()) :: {:ok, Macro.t(), Macro.Env.t()} | {:error, term()}
Expand all macros in a pre-parsed AST with an explicit environment.
Returns both the expanded AST and the final environment state.
Examples
iex> env = ExPanda.EnvManager.new_env()
iex> {:ok, ast} = Code.string_to_quoted("unless true, do: :never")
iex> {:ok, expanded, _final_env} = ExPanda.expand(ast, env, [])
iex> match?({:case, _, _}, expanded)
true
Expand all macros in an Elixir source file.
Options
Same as expand_string/2. The :file option defaults to the given path.
Examples
{:ok, expanded} = ExPanda.expand_file("lib/my_module.ex")
Expand all macros in a source code string.
Options
:env- CustomMacro.Envto use as the base environment. Defaults to a fresh environment from:elixir_env.new().:file- File path to set in the environment (for error messages). Defaults to"nofile".:preserve_lines- Whether to preserve line/column metadata in parsed AST. Defaults totrue.
Examples
iex> {:ok, expanded} = ExPanda.expand_string("unless true, do: :never")
iex> match?({:case, _, _}, expanded)
true
Expand all macros and return formatted Elixir code.
Accepts either a source code string or a pre-parsed AST.
The result is formatted with Code.format_string!/2, preserving
whitespace in docstrings.
Options
When given a string, accepts the same options as expand_string/2.
When given an AST, accepts the same options as expand/2.
Additionally:
:format- Options passed toCode.format_string!/2. Defaults to[].
Examples
iex> {:ok, code} = ExPanda.expand_to_string("unless true, do: :never")
iex> code =~ "case"
true
iex> {:ok, ast} = Code.string_to_quoted("1 |> to_string()")
iex> {:ok, code} = ExPanda.expand_to_string(ast)
iex> code =~ "String.Chars.to_string"
true