Skip to content

Instantly share code, notes, and snippets.

@uasi
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uasi/f2ef5cce22b9ea4b0ba4 to your computer and use it in GitHub Desktop.
Save uasi/f2ef5cce22b9ea4b0ba4 to your computer and use it in GitHub Desktop.
Elixir で Plug.Builder 相当の DSL を再実装してマクロの展開順を追ってみた
### 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
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