RubyでPackratパーサ続き

メモ化絡みのロジックがやっぱりまだおかしかったので修正
デバッグしやすいように、メソッド名が(非)終端記号名を反映するようにした。rubyの例外のスタックトレース表示に合わせた改良
その他細かい追加とか
固定ページ作ってそっちでやるかな...
追記: http://metanest.jp/peg-packrat/peg-packrat.xhtml にとりあえずできたものを置いた

#!/usr/local/bin/ruby19 -w
# coding:utf-8
# vi:set ts=3 sw=3:
# vim:set sts=0 noet:

require 'pp'

class Derivs
   DerivTbl = {}

   def self.add_parser tag, parser
      DerivTbl[tag] = parser
   end

   def initialize str
      @memo = Hash.new do|hash, tag|
         case
         when parser = DerivTbl[tag] then
            parser.send :"force_#{tag.to_s}", self
         when tag == :dv_char
            if str.empty? then
               nil
            else
               [str[0, 1], ::Derivs.new(str[1..-1])]
            end
         when tag == :dv_input
            str
         end
      end
   end

   def [] tag
      @memo[tag]
   end
end

class Parser
   def Parser.create tag=nil, &code
      if tag and code then raise end
      unless tag or code then raise end
      (tag && ParserTop.new(tag)) or (code && ParserSub.new(code))
   end
end

class ParserTop < Parser
   def initialize tag
      Derivs.add_parser tag, self
      @tag = tag
      eval "def self.force_#{tag.to_s} d
         @func.parse d
      end\n"
   end

   def set parser
      @func = parser
   end

   def parse d
      d[@tag]
   end
end

class ParserSub < Parser
   def initialize code
      @func = code
   end

   def parse d
      @func.call d
   end
end

class Parser
   def / other
      Parser.create do|d|
         self.parse d or other.parse d
      end
   end

   def !
      Parser.create do|d|
         if self.parse d then
            nil
         else
            [nil, d]
         end
      end
   end

   def *
      Parser.create do|d|
         list = []
         while r = self.parse(d) do
            sym, d = r
            list << sym
         end
         [list, d]
      end
   end

   def +
      tail_parser = self.*
      Parser.create do|d|
         if r = self.parse(d) then
            head, d = r
            r = tail_parser.parse d
            tail, d = r
            tail.unshift head
            [tail, d]
         else
            nil
         end
      end
   end

   def opt
      Parser.create do|d|
         self.parse d or [nil, d]
      end
   end
end

module ParserUtil
   def seq *parsers
      Parser.create do|d|
         result = parsers.inject []do|list, parser|
            if r = parser.parse(d) then
               v, d = r
               list << v
            else
               break nil
            end
         end

         if result then
            if block_given? then
               [yield(result), d]
            else
               [result, d]
            end
         else
            nil
         end
      end
   end

   def chr c
      Parser.create do|d|
         if r = d[:dv_char] then
            cc, dd = r
            if cc == c then
               [cc, dd]
            else
               nil
            end
         else
            nil
         end
      end
   end

   def str s
      result = s.split('').inject []do|list, c|
         parser = chr c
         list << parser
      end
      wrap(seq(*result)){|list| list.join}
   end

   def wrap parser
      Parser.create do|d|
         if r = parser.parse(d) then
            if block_given? then
               v, dd = r
               [yield(v), dd]
            else
               r
            end
         else
            nil
         end
      end
   end

   def ref parser
      Parser.create do|d|
         if r = parser.parse(d) then
            v, dd = r
            [v, d]
         else
            nil
         end
      end
   end

   # combinator for (elem (, elem)*)? , returns [elem0, elem1, elem2, ...]
   def aba elem, comma
      tail_parser = (seq(comma, elem){|_, e| e}).*
      Parser.create do|d|
         if r = elem.parse(d) then
            head, d = r
            tail, d = tail_parser.parse d
            tail.unshift head
            [tail, d]
         else
            [[], d]
         end
      end
   end

   # combinator for elem (, elem)* , returns [elem0, elem1, elem2, ...]
   def aba1 elem, comma
      tail_parser = (seq(comma, elem){|_, e| e}).*
      Parser.create do|d|
         if r = elem.parse(d) then
            head, d = r
            tail, d = tail_parser.parse d
            tail.unshift head
            [tail, d]
         else
            nil
         end
      end
   end
end

module P
   extend ParserUtil

   Start = Parser.create :start

   Foo = Parser.create :foo

   Additive = Parser.create :additive
   Multitive = Parser.create :multitive
   Primary = Parser.create :primary
   Decimal = Parser.create :decimal
   DecimalChar = Parser.create :decimalChar

   # any char
   Char = Parser.create do|d|
      d[:dv_char]
   end

   # Because of priority, prefer {...} block than do...end .

   Start.set(
      seq(Additive, !Char)
   )

   Additive.set(
      seq(Multitive, chr('+'), Additive){|l, _, r| l + r} / Multitive
   )

   Multitive.set(
      seq(Primary, chr('*'), Multitive){|l, _, r| l * r} / Primary
   )

   Primary.set(
      seq(chr('('), Additive, chr(')')){|args| args[1]} / Decimal
   )

   Decimal.set(
      wrap(DecimalChar){|c| Integer c}
   )

   DecimalChar.set(
      chr('0') / chr('1') / chr('2') / chr('3') / chr('4') /
      chr('5') / chr('6') / chr('7') / chr('8') / chr('9')
   )
end

d = Derivs.new '(2+3)*(4+5)'
pp d[:start]