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]