Last active
August 29, 2015 14:22
-
-
Save uasi/f2ef5cce22b9ea4b0ba4 to your computer and use it in GitHub Desktop.
Elixir で Plug.Builder 相当の DSL を再実装してマクロの展開順を追ってみた
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### start from here ### | |
defmodule MyApp do | |
use MyPlugBuilder | |
plug :hello | |
plug :world, good: :morning | |
end | |
### expand `use MyPluginBuilder` macro ↓ ### | |
defmodule MyApp do | |
# {{{ | |
require MyPlugBuilder | |
MyPlugBuilder.__using__([]) | |
# }}} | |
plug :hello | |
plug :world, good: :morning | |
end | |
### expand `MyPlugBuilder.__using__` macro ↓ ### | |
defmodule MyApp do | |
require MyPlugBuilder | |
# {{{ | |
import MyPlugBuilder, only: [plug: 1, plug: 2] | |
Module.register_attribute(__MODULE__, :plugs, accumulate: :true) | |
@before_compile MyPlugBuilder | |
# }}} | |
plug :hello | |
plug :world, good: :morning | |
end | |
### expand `plug` macro ↓ ### | |
defmodule MyApp do | |
require MyPlugBuilder | |
import MyPlugBuilder, only: [plug: 1, plug: 2] | |
Module.register_attribute(__MODULE__, :plugs, accumulate: :true) | |
@before_compile MyPlugBuilder | |
# {{{ | |
@plugs {:hello, []} | |
@plugs {:world, [good: :morning]} | |
# }}} | |
end | |
### inject `Module.__before_compile__` thanks to @before_compile ↓ ### | |
# http://elixir-lang.org/docs/stable/elixir/Module.html | |
# > [If __before_compile__ is] a macro, its returned value will be injected | |
# > at the end of the module definition before the compilation starts. | |
defmodule MyApp do | |
require MyPlugBuilder | |
import MyPlugBuilder, only: [plug: 1, plug: 2] | |
Module.register_attribute(__MODULE__, :plugs, accumulate: :true) | |
@before_compile MyPlugBuilder | |
@plugs {:hello, []} | |
@plugs {:world, [good: :morning]} | |
# {{{ | |
Module.__before_compile__(__ENV__) | |
# }}} | |
end | |
### expand `Module.__before_compile__` macro ↓ ### | |
defmodule MyApp do | |
require MyPlugBuilder | |
import MyPlugBuilder, only: [plug: 1, plug: 2] | |
Module.register_attribute(__MODULE__, :plugs, accumulate: :true) | |
@before_compile MyPlugBuilder | |
@plugs {:hello, []} | |
@plugs {:world, [good: :morning]} | |
# {{{ | |
def plugs, do: [world: [good: :morning], hello: []] | |
# }}} | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyPlugBuilder do | |
# `use MyPluginBuilder` するとこれが呼ばれる | |
# | |
defmacro __using__(_opts) do | |
quote do | |
import MyPlugBuilder, only: [plug: 1, plug: 2] | |
Module.register_attribute(__MODULE__, :plugs, accumulate: :true) | |
@before_compile MyPlugBuilder | |
end | |
end | |
defmacro plug(plug, opts \\ []) do | |
quote do | |
@plugs {unquote(plug), unquote(opts)} | |
end | |
end | |
# `@before_compile MyPluBuilder` 属性を含む翻訳単位がコンパイルされる直前に、 | |
# 属性と同じコンテキストで呼ばれる? | |
# | |
defmacro __before_compile__(env) do | |
plugs = Module.get_attribute(env.module, :plugs) | |
quote do | |
def plugs, do: unquote(plugs) | |
end | |
end | |
end | |
defmodule MyApp do | |
use MyPlugBuilder | |
plug :hello | |
plug :world, good: :morning | |
end | |
IO.puts "plugs >>> #{inspect MyApp.plugs}" | |
# => plugs >>> [world: [good: :morning], hello: []] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment