せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【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
ハッシュリテラルがメソッドの最後の引数なら(あるいは、その後ろに続くのがブロック引数だけなら)、ハッシュリテラルを囲む中かっこを省略できるようにしている。中かっこなしのハッシュは、裸のハッシュ(bare hash)と呼ばれることがあり・・・

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

正式なリファレンスがググりきれなかったので、本からの引用


 

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

 


疲れたぶー。