【Ruby】【メタプログラミング】言語内DSL(文法チェックあり)
■topic summary
study about DSL. it has grammer check.
昨日作成した言語内DSLは文法チェックがなかったけど、
今回は文法チェックを追加したパターン。
学んだこと
親クラスで暮らすメソッドを定義することにより、
子クラスで地の文チックに使用できる
⇒elementメソッド
class_eval内でのメソッド定義
class_eval %Q{ def #{tagname}(attributes={} ,&b) tag(:#{tagname}, attributes ,&b) end }
のところ。
なぜtagnameを展開してシンボル化するのか。
第二引数のattributesはなぜ#{}展開がいらないのか
を理解しました。
これは別記事にて詳しく書きました。
裸のハッシュ(※1)の後に
ブロックを続ける場合は{}ではなくdo-endを使う。
※1
6.4.4
http://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E-Ruby-%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8-%E3%82%86%E3%81%8D%E3%81%B2%E3%82%8D/dp/4873113946
ハッシュリテラルがメソッドの最後の引数なら(あるいは、その後ろに続くのがブロック引数だけなら)、ハッシュリテラルを囲む中かっこを省略できるようにしている。中かっこなしのハッシュは、裸のハッシュ(bare hash)と呼ばれることがあり・・・
正式なリファレンスがググりきれなかったので、本からの引用
DSLコード
HTMLForm.generate(STDOUT) do comment "simple form" form name: "registration", action: "http://hoge" do content "Adress:" textarea name: "address", rows:6, cols: 40 do "please enter your address" end end end
■実行結果
<!-- simple form --><form name='registration' action='http://hoge' method='GET' entype='application/www'>Adress:<textarea name='address' rows='6' cols='40' readonly='readonly'>please enter your address</textarea></form>
実装
class XMLGrammer def initialize(out) @out = out end def self.generate(out, &b) self.new(out).instance_eval(&b) end def self.element(tagname, attributes={} ,&b) @allowed_attributes ||= {} @allowed_attributes[tagname] = attributes class_eval %Q{ def #{tagname}(_attributes={} ,&b) tag(:#{tagname}, _attributes ,&b) end } end def self.allowed_attributes @allowed_attributes end def tag(tagname, attributes={}, &b) allowed = self.class.allowed_attributes[tagname] @out << "<#{tagname}" #認められた属性か attributes.each do |key, val| raise "unknown attribute" if !allowed.include?(key) @out << " #{key}='#{val}'" end #デフォルト値を出力 allowed.each do |key, val| next if attributes.include?(key) if val == REQ raise "required attribute missing" elsif val == BOOL @out << " #{key}='#{key}'" elsif val.is_a? String @out << " #{key}='#{val}'" end end @out << ">" if block_given? content = yield @out << content if content end @out << "</#{tagname}>" nil end def comment(text) @out << "<!-- #{text} -->" nil end def content(text) @out << "#{text}" end OPT = :opt REQ = :req BOOL = :bool end class HTMLForm < XMLGrammer element :form, action: REQ, method: "GET", entype: "application/www", name: OPT element :textarea, name: REQ, rows: REQ, cols: REQ, readonly: BOOL end
疲れたぶー。