Brainf*ck

class Brainfuck
options no_result_var
rule
  exp:
     | exp ope

  ope: '>'
       {
         @ptr += 1
       }
     | '<'
       {
         @ptr -= 1
       }
     | '+'
       {
         @ary[@ptr] ||= 0
         @ary[@ptr] += 1
       }
     | '-'
       {
         @ary[@ptr] ||= 0
         @ary[@ptr] -= 1
       }
     | '.'
       {
         print (@ary[@ptr] || 0).chr
       }
     | ','
       {
         @ary[@ptr] = $stdin.getc # XXX: Ruby 1.9では動作が異なる
       }
     | '['
       {
         if (@ary[@ptr] || 0).zero?
           @ss.skip(/[^\]]*\]/)
         else
           @stack.push('')
         end
       }
     | ']'
       {
         if (buf = @stack.pop)
           @ss = StringScanner.new(buf + ']' + @ss.rest)
         end
       }
end

---- header

require 'strscan'
require 'readline'

---- inner

attr_reader :ptr
attr_reader :ary
attr_reader :stack

def initialize
  @ptr = 0
  @ary = []
  @stack = []
end

def scan
  until @ss.eos?
    if tok = @ss.scan(/[><\+\-\.,\[\]]/)
      yield tok, tok
      @stack.last << tok if @stack.last
    elsif tok = @ss.scan(/[^><\+\-\.,\[\]]/)
      # nothing to do
    end
  end

  yield false, '$'
end

def parse(src)
  @ss = StringScanner.new(src)
  yyparse self, :scan
end

---- footer

parser = Brainfuck.new

if ARGV.empty?
  while buf = Readline.readline('brainfuck> ', true)
    parser.parse(buf).inspect
    puts <<-EOS
---
ptr: #{parser.ptr}
ary: #{parser.ary.inspect}
stack: #{parser.stack.inspect}

    EOS
  end
else
  ARGV.each do |fn|
    parser.parse(open(fn) {|f| f.read }).inspect
  end
end