[Elixir]ASTをトレースして出力するマクロを作る

#Goal
マクロを再帰的に展開しながらコンソールへ出力する。

#Dev-Environment
OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

#Wait a minute
マクロを再帰的に展開し、
iexのコンソールへ結果を表示していくプログラムを作ります。

#Index
Macro to trace the AST
|> Tracing AST
|> Let's run!
|> Extra

##Tracing AST
一々、マクロをexpand_once/2で展開していくのは面倒ですね。

なので、ASTを渡すと再帰的にASTを展開して、
内容を出力していくプログラムを実装します。

Example:

```elixir
defmodule AstTracer do
  defmacro print(ast) do
    quote do
      IO.puts "\nTrace start...\n"

      IO.puts "[Unexpanded]"
      AstTracer.expr_print(unquote(ast))
      AstTracer.ast_print(unquote(ast))
      IO.puts "================================\n"

      AstTracer.trace_print(
        Macro.expand_once(unquote(ast), __ENV__), unquote(ast), 1)
    end
  end

  def trace_print(ast, before_ast, count) do
    cond do
      ast == before_ast or is_nil(ast) ->
        IO.puts "...Trace end"
      true ->
        IO.puts "[Expand: #{count}]"
        expr_print(ast)
        ast_print(ast)
        IO.puts "================================\n"
        trace_print(Macro.expand_once(ast, __ENV__), ast, count + 1)
    end
  end

  def expr_print(expression_ast) do
    IO.puts "========== Expression =========="
    IO.puts "#{Macro.to_string(expression_ast)}"
  end

  def ast_print(expression_ast) do
    IO.puts "============= AST =============="
    IO.inspect expression_ast
  end
end
```

##Let's run!
実際に使ってみましょう！

iexを起動して、以下をrequireして下さい。

```elixir
iex> require AstTracer
nil
```

まずは、1 + 2を試します。

Result:

```elixir
iex> quoted = quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
iex> AstTracer.print(quoted)

Trace start...

[Unexpanded]
========== Expression ==========
1 + 2
============= AST ==============
{:+, [context: Elixir, import: Kernel], [1, 2]}
================================

...Trace end
:ok
```

展開がないので微妙ですね。

2,3回展開するものでぱっと思いつくのは・・・

unlessを展開させましょう！

Result:

```elixir
iex> quoted = quote do: unless(true, do: false, else: true)
{:unless, [context: Elixir, import: Kernel], [true, [do: false, else: true]]}
iex> AstTracer.print(quoted)

Trace start...

[Unexpanded]
========== Expression ==========
unless(true) do
  false
else
  true
end
============= AST ==============
{:unless, [context: Elixir, import: Kernel], [true, [do: false, else: true]]}
================================

[Expand: 1]
========== Expression ==========
if(true) do
  true
else
  false
end
============= AST ==============
{:if, [context: Kernel, import: Kernel], [true, [do: true, else: false]]}
================================

[Expand: 2]
========== Expression ==========
case(true) do
  x when x in [false, nil] ->
    false
  _ ->
    true
end
============= AST ==============
{:case, [optimize_boolean: true],
 [true,
  [do: [{:->, [],
     [[{:when, [],
        [{:x, [counter: 59], Kernel},
         {:in, [context: Kernel, import: Kernel],
          [{:x, [counter: 59], Kernel}, [false, nil]]}]}], false]},
    {:->, [], [[{:_, [], Kernel}], true]}]]]}
================================

...Trace end
:ok
```

おぉ、一杯出力された(笑)
動作的には問題ないようですね。

##Extra
Phoenix-Frameworkのweb.ex(using)を展開させてみました。

Result:

```elixir
iex> require PhoenixV1_0_0Sample.Web
nil
iex> require AstTracer
nil
iex> quoted = quote do: PhoenixV1_0_0Sample.Web.__using__(:router)
{{:., [],
  [{:__aliases__, [alias: false], [:PhoenixV1_0_0Sample, :Web]}, :__using__]},
 [], [:router]}
iex> AstTracer.print(quoted)

Trace start...

[Unexpanded]
========== Expression ==========
PhoenixV1_0_0Sample.Web.__using__(:router)
============= AST ==============
{{:., [],
  [{:__aliases__, [alias: false], [:PhoenixV1_0_0Sample, :Web]}, :__using__]},
 [], [:router]}
================================

[Expand: 1]
========== Expression ==========
use(Phoenix.Router)
============= AST ==============
{:use, [context: PhoenixV1_0_0Sample.Web, import: Kernel],
 [{:__aliases__, [alias: false, counter: 54], [:Phoenix, :Router]}]}
================================

[Expand: 2]
========== Expression ==========
(
  require(Phoenix.Router)
  Phoenix.Router.__using__([])
)
============= AST ==============
{:__block__, [],
 [{:require, [context: Kernel, counter: 55], [Phoenix.Router]},
  {{:., [], [Phoenix.Router, :__using__]}, [], [[]]}]}
================================

...Trace end
:ok
```

#Speaking to oneself
必要かどうかはともかく、「やってみたかった」この一言に尽きます。
(完全に自己満足の世界でした(笑))

余暇で作ったプログラムなのでこんなものでしょう。

マクロを作る時や作られたマクロを見るときに少しでも役に立てば嬉しいです。

#Bibliography
[hexdocs - Elixir(v1.0.5) - Macro](http://elixir-lang.org/docs/v1.0/elixir/Macro.html)