RubyでPackratパーサ続き

メモ化が全く効いてないという大バグを直しながらリファクタリング
(追記:メモ化絡みでロジックがまだ変。修正版は id:metanest:20090406 に)

#!/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 initialize s
      @memo = Hash.new do|d, tag|
         if parser = DerivTbl[tag] then
            parser.force d
         elsif tag == :char
            if s.empty? then
               nil
            else
               [s[0, 1], Derivs.new(s[1..-1])]
            end
         elsif tag == :input
            s
         else
            raise
         end
      end
   end

   def [] term
      @memo[term]
   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::DerivTbl[tag] = self
      @memo_tag = tag
   end

   def set parser
      @func = parser
   end

   def force d
      @func.parse d
   end

   def parse d
      d[@memo_tag] or force d
   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 char c
      Parser.create do|d|
         if r = d[:char] then
            cc, dd = r
            if cc == c then
               [cc, dd]
            else
               nil
            end
         else
            nil
         end
      end
   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 success v
      Parser.create do|d|
         [v, d]
      end
   end

   def fail
      Parser.create do|d|
         nil
      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
end

module P
   extend ParserUtil

   Start = Parser.create :start

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

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

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

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

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

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

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

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

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

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