Befunge

前回勢いで書いてしまった奴は全然イケテナカッタみたいで、仕様は洩れまくり。コンマとピリオドの違いもちゃんと実装していなかったし、文字列指定"の動作も間違っていて、文字列中に befunge文法要素が出てしまうとそっちを実行してしまうひどいもんだった。いくら速攻とは家ひどすぎる。修正してみた。
#なぜかバグってるほうをアップしてしまった。再度修正
これで

"@"00pv
^     <
sikibu:befunge kuro$ ./befunge.rb -d selfwrite.bf 
pos: (0 , 0) mnemonic: " dir: 3 stack: 
pos: (1 , 0) mnemonic: @ dir: 3 stack: 
pos: (2 , 0) mnemonic: " dir: 3 stack: 64
pos: (3 , 0) mnemonic: 0 dir: 3 stack: 64
pos: (4 , 0) mnemonic: 0 dir: 3 stack: 64,0
pos: (5 , 0) mnemonic: p dir: 3 stack: 64,0,0
pos: (6 , 0) mnemonic: v dir: 3 stack: 
pos: (6 , 1) mnemonic: < dir: 1 stack: 
pos: (5 , 1) mnemonic:   dir: 2 stack: 
pos: (4 , 1) mnemonic:   dir: 2 stack: 
pos: (3 , 1) mnemonic:   dir: 2 stack: 
pos: (2 , 1) mnemonic:   dir: 2 stack: 
pos: (1 , 1) mnemonic:   dir: 2 stack: 
pos: (0 , 1) mnemonic: ^ dir: 2 stack: 
pos: (0 , 0) mnemonic: @ dir: 0 stack: 

のように自己書き換えや、普通にユーザー入力も

&&+.25*,@
sikibu:befunge kuro$ ./befunge.rb add.bf 
123
321
444

おk。
カウンタを作ってこんなことも

25*66+8p 0v
   > 1 +  v  
   ,      :  
   g      6  
   8      :  
          +
   :      `  
   ^     @_  
HELLO WORLD!!

できる。

sikibu:befunge kuro$ ./befunge.rb hello.bf 
HELLO WORLD!

残りの仕様も実装完了。こんなもんかな。全然テストしてないけど・・・

class VM
   class Point 
      attr_accessor :x, :y
      def initialize(x,y)
         @x = x
         @y = y
      end
      def to_s
         "(#{x} , #{y})"
      end
   end

   attr_reader :dir
   UP = 0
   DOWN =  1
   LEFT =  2
   RIGHT = 3
   DIR = {'>'=>RIGHT, '<'=>LEFT, '^'=>UP, 'v'=>DOWN}

   attr_reader :state
   MODE_NORMAL = 0
   MODE_STRING = 1

   attr_reader :curpos
   attr_reader :stack
   attr_reader :prog

   OP = {
      '+' => Proc.new { |x,y| x + y },
      '-' => Proc.new { |x,y| x - y },
      '*' => Proc.new { |x,y| x * y },
      '/' => Proc.new { |x,y| x / y },
      '%' => Proc.new { |x,y| x % y }
   }

   def read_raw(p)
      @prog[p.y][p.x]
   end

   def write_raw(p, v)
      @prog[p.y][p.x] = v
   end

   def read
      read_raw(@curpos)
   end

   def initialize(prog=nil)
      @stack = []
      @curpos = Point.new(0,0)
      @dir = RIGHT
      @state = MODE_NORMAL
      @debug = false
      if prog then load_program(prog) end
   end

   def load_program(prog)
      @prog = prog.split("\n")
      @width  = @prog.map {|l| l.length}.max
      @height = @prog.length
   end

   def nextpos
      case @dir
      when RIGHT
         @curpos.x = (@curpos.x+1) % @width
      when LEFT
         @curpos.x = (@curpos.x-1) % @width
      when UP
         @curpos.y = (@curpos.y-1) % @height
      when DOWN
         @curpos.y = (@curpos.y+1) % @height
      end
   end

   # utils for isrns
   def doublequote
      @state = (MODE_STRING+MODE_NORMAL) - @state
   end

   def changedir(pred, oneway, anotherway)
      @dir = (pred == 0)? oneway : anotherway
   end

   def swaplast2
      t = @stack.pop
      @stack.push(@stack.pop,t)
   end

   def alu(op)
      y = @stack.pop
      x = @stack.pop
      @stack.push OP[op].call(x,y)
   end

   def cmp
      y = @stack.pop
      x = @stack.pop
      @stack.push  x > y ? 1 : 0
   end
   
   def negate
      @stack.push  @stack.pop == 1? 0 : 1
   end

   def get
      y = @stack.pop
      x = @stack.pop
      @stack.push  read_raw(Point.new(x,y))
   end

   def put
      y = @stack.pop
      x = @stack.pop
      v = @stack.pop.chr
      write_raw(Point.new(x,y), v) 
   end

   # main dispatcher
   def step(n=1)
      c = read

      if @debug
         STDERR.print "pos: #{@curpos} mnemonic: #{c.chr} "
         STDERR.puts  "dir: #{@dir} stack: #{@stack.join(',')}"
      end

      if @state == MODE_STRING && c != ?"
         @stack.push(c)
      else 
         case c

            #control
         when ?v,?<,?>,?^     then @dir = DIR[c.chr]
         when ?_              then changedir(@stack.pop, RIGHT, LEFT)
         when ?|              then changedir(@stack.pop,    UP, DOWN)
         when ??	      then @dir = rand(4)
         when ?#	      then nextpos    # through!
         when ?@              then return false

            #literal
         when ?0 .. ?9        then @stack.push c.chr.to_i
         when ?"              then doublequote

            #I/O
         when ?&              then @stack.push STDIN.readline.to_i
         when ?~              then @stack.push STDIN.getc
         when ?.              then print @stack.pop
         when ?,              then print @stack.pop.chr

            #arith & logic 
         when ?+,?-,?*,?/,?%  then alu(c.chr)
         when ?`              then cmp
         when ?!	      then negate

            #stack manip
         when ?:              then @stack.push @stack.last
         when ?\\	      then swaplast2
         when ?$	      then @stack.pop   #just discard

            #mem manip
         when ?g	      then get
         when ?p              then put
         end
      end
      nextpos
   end

   def debugenable
      @debug = true
   end
end


vm = VM.new

if ARGV[0] == "-d"
   ARGV.shift
   vm.debugenable
end

vm.load_program(File.open(ARGV[0]).read)

while vm.step
end