DSL の完成品の前段階で、そもそもの DSL 文を ruby が拾えるようになる瞬間
# オブジェクトの能力値の判定する DSL文 を作る。
# 最初は判定オブジェクトの存在さえも気にせずに書き下してよい。
def dsl_sample()
# watchman も setup も作っていなくても、あとから環境を与えられるのが ruby。
watchman "you are tough" do
@strength > 100
end
watchman "you are feeble" do
@strength < 50
end
watchman "you are rapid" do
@agility > 100
end
watchman "you are slow" do
@agility < 50
end
watchman "upper limit" do
@level > 20
end
watchman "lower limit" do
@level < 5
end
setup do
@strength = 80
@agility = 120
end
setup do
@level = 30
end
end
# 適当に作った、watchman, setup を動かす。すべてのブロックは、環境(ローカル変数、インスタンス変数、self)が与えられて動くので、それを後から自在に与えればよい。
# 無駄な名前空間汚染を防ぐ。
lambda {
# ファイルの読み込み後、全 DSL を格納する配列とハッシュを用意する。あとで適切な実行行うため。
setups = []
events = {}
# def キーワードを使わないことが、スコープゲートの回避
Kernel.module_eval do
# setup と watchman を拾うために、Kernel にメソッドを作る。setups と events に格納したいのでスコープゲートを回避する define_method を使う。
define_method :setup do |&block|
setups << block
end
define_method :watchman do |event_name, &block|
events[event_name] = block
end
end
}.call
dsl_sample() # 動く!DSLのブロックは実行されていない。
任意の each 文を実装する場面
event毎に調査していき、「環境」を新しく作り setup をかまして、調査したい場合、以下のようなメインコードになったとする。Cleanroom 技術を使っている。
each_event do |event_name, event|
environment = Cleanroom.new
each_setup do |setup|
environment.instance_eval(&setup)
end
puts event_name if environment.instance_eval(&event)
end
each_event と each_setup を動かすために Kernel の define_method を追加する。
lambda {
setups = []
events = {}
Kernel.module_eval do
define_method :setup do |&block|
setups << block
end
define_method :watchman do |event_name, &block|
events[event_name] = block
end
# スコープに setup が渡るようにする。
# block.call setup を作れば、第1引数に setup が渡ってくる。
define_method :each_setup do |&block|
setups.each do |setup|
block.call setup
end
end
# 第1引数に イベント名、第2引数にイベントProc が渡ってくるようにする。
define_method :each_event do |&block|
# すでに Proc が渡ってくるので、& は不必要
events.each_pair do |entry_name, event|
block.call entry_name, event
end
end
end
class Cleanroom
end
}.call