Rzubr::Rule に生成規則メソッドのサポートを追加

Rzubr v0.0.3 から、生成規則メソッドを利用できるようにしました

https://github.com/tociyuki/rzubr-ruby

これまでの Rzubr::Rule は生成規則記述に式を使うしかありませんでした。式を使って手続きの中で構文を組み立てていくのには良いのですが、Parsec 系統のパーザ・コンビネータのように生成規則を手続き小分けしたところ、記述が冗長になることに気がつきました。冗長になる理由を考えてみるに、構文解析表を作るのにバッカス-ナウア記法 (BNF) の左辺と右辺の両方を必要とし、次の例のようにメソッド名と左辺記号が被りがちだからのようです。rule_program メソッドは、左辺が program 非終端記号の生成規則を定義しており、メソッド名の program と左辺記号 program が重複しています。rule_declaration メソッドも同様です。

class PrettyANSICDeclaration
  def grammar_table
    s = rule_program \
      + rule_declaration \
      + rule_declaration_specifiers #+ 以下略
    Rzubr::LALR1.new.rule(s.start(:program))
  end

  def rule() Rzubr::Rule end
  alias :seq :rule

  def rule_program
    rule.name(:program) > seq[:declaration] | seq[:program, :declaration]
  end

  def rule_declaration
    rule.name(:declaration) \
      > seq[:declaration_specifiers, ';'] & :puts_declaration \
      | seq[:declaration_specifiers,  :declarator_list, ';'] & :puts_declaration_list
  end
  def puts_declaration(v) puts v[1]; nil end
  def puts_declaration_list(v) print v[2].collect{|x| "#{x} #{v[1]}.\n" }.join; nil end

  #略
end

そこで、メソッド名から BNF の左辺記号を得て、メソッドボディから右辺を得て、生成規則を生成できるようにしてみました。クラスが必須になる欠点があるものの、全体として記述が簡潔になります。Rzubr::Rule の クラスメソッド productions_in の引数に self を与えると、self.class のパブリック・メソッドの中から rule_ で始まるメソッドを拾い上げて、生成規則を組み立てます。生成規則の左辺記号に、メソッド名の先頭の rule_ を省いてシンボルにしたものを使います。これが返すのは、式で組み立てる構文記述オブジェクトそのものであり、これと終端記号の優先度指定式などを足し合わせることができます。このような生成規則を作るためのメソッドを生成規則メソッドと便宜上呼ぶことにします。

上の例を生成規則メソッドを使って書き直すと下の例になります。

class PrettyANSICDeclaration
  def grammar_table
    Rzubr::LALR1.new.rule(Rzubr::Rule.productions_in(self).start(:program))
  end

  def seq() Rzubr::Rule end

  def rule_program
    seq[:program, :declaration] | seq[:declaration]
  end

  def rule_declaration
    seq[:declaration_specifiers, ';'] & :puts_declaration \
  | seq[:declaration_specifiers,  :declarator_list, ';'] & :puts_declaration_list
  end
  def puts_declaration(v) puts v[1]; nil end
  def puts_declaration_list(v) print v[2].collect{|x| "#{x} #{v[1]}.\n" }.join; nil end

  #略
end

ただし、この機能を付けてみたのは良いのですけど、式だけで記述する方式の方が左辺記号が明解で、コードを読む立場には親切かもしれないという懸念が心の中にくすぶっています。