対応する括弧を行き来する感覚を大事にする為にその場で実行する方向で考える。ソースを行き来できるようにするわけだ。その為に、ソースとソース上の位置を示すポインタを導入する。
class BrainF_ckParser rule# class BrainF_ckParser expression : | expression TERMINAL { send val[1] } end # class BrainF_ckParser ---- inner def initialize(source) @source = source.split // @source_pointer = 0 @tape = [] @pointer = 0 end # def initialize(source) def parse(opts = nil) @yydebug = opts.yydebug if opts.respond_to? :yydebug yyparse self, :scan end # def parse(opts = nil) def right @pointer += 1 end # def right def left @pointer -= 1 end # def left def plus @tape[@pointer] = (@tape[@pointer] ||= 0) + 1 end # def plus def minus @tape[@pointer] = (@tape[@pointer] ||= 0) - 1 end # def minus def put print (@tape[@pointer] ||= 0).chr end # def put def get c = $stdin.getc @tape[@pointer] = c.respond_to?(:ord) ? c.ord : c end # def get def bra if (@tape[@pointer] ||= 0) == 0 then @source_pointer = corresponding_bracket(@source_pointer) - 1 end # if (@tape[@pointer] or 0) == 0 end # def bra def ket unless (@tape[@pointer] ||= 0) == 0 then @source_pointer = corresponding_bracket(@source_pointer) - 1 end # unless (@tape[@pointer] or 0) == 0 end # def ket private def scan while c = @source[@source_pointer] do case c when '>' then yield [:TERMINAL, :right] when '<' then yield [:TERMINAL, :left] when '+' then yield [:TERMINAL, :plus] when '-' then yield [:TERMINAL, :minus] when '.' then yield [:TERMINAL, :put] when ',' then yield [:TERMINAL, :get] when '[' then yield [:TERMINAL, :bra] when ']' then yield [:TERMINAL, :ket] end # case c @source_pointer += 1 end # while c = @source[@source_pointer] do yield nil end # def scan Correspondings = { '[' => ['[', ']', +1 ], ']' => [']', '[', -1 ], } def corresponding_bracket(s_pointer) bra_ket, ket_bra, sign = Correspondings[@source[s_pointer]] s_pointer += sign corr = 0 until corr == 0 and @source[s_pointer] == ket_bra do raise unless (0...@source.size).include? s_pointer corr += 1 if @source[s_pointer] == bra_ket corr -= 1 if @source[s_pointer] == ket_bra s_pointer += sign end # until corr == 0 and @source[s_pointer] == ket_bra do s_pointer end # def corresponding_bracket(s_pointer) ---- header # -*- coding: utf-8; -*- Version = '$Id: brainf_ck_source_pointer.y 3298 2009-03-07 02:50:25Z hs9587 $' # $Date: 2009-03-07 11:50:25 +0900 (土, 07 3 2009) $ ---- footer require 'optparse' require 'ostruct' opts = OpenStruct.new opts.yydebug = false ARGV.options do |opt| opt.on('--[no-]yydebug', 'debug mode (from racc -t) on/off'){ |v| opts.yydebug = v } opt.release = 'TERMINAL acts directly' opt.banner += "\n\tBrainf*ck interpreter.\nOptions:" opt.parse! end # ARGV.options do |opt| BrainF_ckParser.new((ARGV.size > 0 ? ARGF : $stdin).read).parse(opts)
もう文法要素は最低限、終端記号も一種類。アクションも単純、そして send で分岐している。
ソース @source は配列にしてみた、文字列のままでも良かったのだけど、1.8系と 1.9系で文字列の要素へのアクセスが変わっているのでちょっと避けた('String'[n])。
corresponding_bracket はちょっと複雑かな、まあ対応する括弧を探してるだけといえばそれだけ、括弧のネストを数えるのが少し煩雑。この中ではテープからはみ出るのチェックしている。あとはこういったソース位置の計算と、スキャナ内でのソース位置の変更(ひとつ先に進む)のタイミングを合わせないといけなかったり。