LoginSignup
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-02

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

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

hexdocs - Elixir(v1.0.5) - Macro

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10