スポンサーリンク

2015年9月2日

[Elixir]Macro to trace the AST

とある錬金術師の万能薬(Elixir)

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:

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して下さい。
iex> require AstTracer
nil
まずは、1 + 2を試します。

Result:

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:

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を展開させてみました。

Result:

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

人気の投稿