Metastatic.Adapters.Elixir.MacroExpander (Metastatic v0.19.0)

View Source

Full macro expansion for the Elixir adapter using ExPanda.

This module takes a surface-level Elixir AST (from Code.string_to_quoted/1) and produces a fully expanded AST where every macro has been resolved, while annotating expansion boundaries with the original surface form so that the FromMeta transformer can reconstruct macros faithfully.

How It Works

  1. Parse the surface AST via Code.string_to_quoted/1 (done upstream)
  2. Expand the AST fully with ExPanda.expand/1
  3. Walk both ASTs in parallel (annotate/2):
    • Structural match (same form atom): recurse into children pairwise
    • Macro boundary (different form atoms): tag the expanded node with __original_macro__: surface_node in its Elixir AST metadata
    • Leaf/literal match: return expanded as-is
  4. Return the annotated expanded AST

The __original_macro__ annotation is later propagated by ToMeta into the MetaAST keyword metadata as :original_macro, and consumed by FromMeta to restore the original macro call during round-tripping.

Examples

iex> {:ok, surface} = Code.string_to_quoted("unless true, do: :never")
iex> {:ok, annotated} = MacroExpander.expand_and_annotate(surface)
iex> {form, meta, _args} = annotated
iex> form
:case
iex> Keyword.has_key?(meta, :__original_macro__)
true

Summary

Functions

Expand all macros in surface_ast via ExPanda and annotate expansion points.

Expand all macros in a source string and return the annotated AST.

Functions

expand_and_annotate(surface_ast, opts \\ [])

@spec expand_and_annotate(
  Macro.t(),
  keyword()
) :: {:ok, Macro.t()} | {:error, term()}

Expand all macros in surface_ast via ExPanda and annotate expansion points.

Returns {:ok, annotated_ast} where every node that resulted from a macro expansion carries __original_macro__: original_surface_node in its Elixir AST metadata keyword list.

Options

  • :env - custom Macro.Env to pass to ExPanda (default: fresh env)
  • :file - file path for error messages (default: "nofile")

Examples

iex> {:ok, surface} = Code.string_to_quoted("1 |> to_string()")
iex> {:ok, annotated} = MacroExpander.expand_and_annotate(surface)
iex> match?({{:., _, _}, _, _}, annotated)
true

expand_and_annotate_string(source, opts \\ [])

@spec expand_and_annotate_string(
  String.t(),
  keyword()
) :: {:ok, Macro.t()} | {:error, term()}

Expand all macros in a source string and return the annotated AST.

Convenience wrapper that parses + expands + annotates in one step.

Examples

iex> {:ok, annotated} = MacroExpander.expand_and_annotate_string("unless true, do: :never")
iex> {form, meta, _} = annotated
iex> form
:case
iex> is_list(Keyword.get(meta, :__original_macro__))
false