Befunge

Wikipediaで見つけた、二次元すごろくプログラミング言語。言語使用を読んだら、思ったよりシンプルだったので作ってみました。
この言語、非常に面白いです。

無限ループ

左上から始まり、右向きに移動して、「<」に来ると、左向きに進みます。
左端に到達すると、今度は右から出てきて、「^」に到達します。
今度は上に進み、「>」→「v」→「<」→「^」→「>」→「v」→「<」→「^」って、無限にループします。

<^
v>

Hello World!を表示

さっきより複雑です。
コマンドの詳細はWikipediaを参照してください。Befunge
矢印の順番に進んで、Hに到達した時点でスタックには、[数値の0,!,d,l,r,o,W,<スペース>,o,l,l,e,H]、このようにデータが積まれてています。その後、スタックの0が出てくるまで、ループして一文字ずつ出力して終了します。

v @_       v
>0"!dlroW"v
v  :#     <
>" ,olleH" v
   ^       <

インタプリタソースコードは以下です。
こんな感じで実行すれば動きます。-dオプションをつけると、いろいろ表示されるかも。

ruby befunge.rb {Befungeのソースファイル}
class Befunge
  E = [1,0]
  W = [-1,0]
  S = [0,1]
  N = [0,-1]

  def initialize
    reset
    init_cmd
  end

  def reset
    @point = [0,0]
    @direction = E
    @stack = []
    @skip = false
    @map = nil
    @height = nil
    @width = nil
    @cmd = {}
    @result = ""
    init_cmd
  end

  def pop
    @stack.pop
  end

  def push x
    @stack.push x
  end

  def init_cmd
    @cmd["<"] = proc{|c|@direction=W}
    @cmd[">"] = proc{|c|@direction=E}
    @cmd["^"] = proc{|c|@direction=N}
    @cmd["v"] = proc{|c|@direction=S}
    @cmd["_"] = proc{|c|@direction=(pop==0)?E: W}
    @cmd["|"] = proc{|c|@direction=(pop==0)?S: N}
    @cmd["?"] = proc{|c|@direction=[N,E,W,S][rand(4)]}
    @cmd[" "] = proc{|c| }
    @cmd["#"] = proc{|c|@skip=true}
    @cmd["@"] = proc{|c|exit!}

    @cmd["\""]= proc{|c|@str_mode=true}

    @cmd["&"] = proc{|c|push STDIN.getc.to_i}
    @cmd["~"] = proc{|c|push STDIN.getc}
    @cmd["."] = proc{|c|x=pop;STD_OUT.print x;STDOUT.flush;@result<<x.to_s}
    @cmd[","] = proc{|c|x=pop;STDOUT.print x.chr;STDOUT.flush;@result<<x.chr}

    @cmd["+"] = proc{|c|y,x=pop,pop;push x+y}
    @cmd["-"] = proc{|c|y,x=pop,pop;push x-y}
    @cmd["*"] = proc{|c|y,x=pop,pop;push x*y}
    @cmd["/"] = proc{|c|y,x=pop,pop;push x/y}
    @cmd["%"] = proc{|c|y,x=pop,pop;push x%y}
    @cmd["/"] = proc{|c|y,x=pop,pop;push x/y}
    @cmd["`"] = proc{|c|y,x=pop,pop;push(x>y ? 1: 0)}
    @cmd["!"] = proc{|c|push pop==0?1:0}
    @cmd[":"] = proc{|c|x=pop;push x;push x}
    @cmd["\\"]= proc{|c|y,x=pop,pop;push x;push x}
    @cmd["$"] = proc{|c|pop}

    @cmd["g"] = proc{|c|y,x=pop,pop;push @map[y][x][0]}
    @cmd["p"] = proc{|c|y,x,v=pop,pop,pop;@map[y][x]=v.chr}
  end

  def read_map(src)
    lines = src.map{|line|line.chomp}
    max_len = lines.max{|x,y|x.size<=>y.size}.size
    @map = lines.map{|line| sprintf("%-*s", max_len, line).split(//)}

    @height = @map.size
    @width = max_len
  end

  def move()
    old_x, old_y = @point
    dir_x, dir_y = @direction
    x = (old_x + dir_x)
    y = (old_y + dir_y)

    y = (y + @height) % @height
    x = (x + @width) % @width

    if $DEBUG
      print "#{[old_x,old_y].inspect} => #{[x,y].inspect}\n"
    end
    @point = [x, y]
  end

  def cur_ch
    x,y = @point
    @map[y][x]
  end

  def do_loop
    while true
      ch = cur_ch
      yield ch
      move
    end
  end

  def eval(src)
    reset
    read_map(src)

    do_loop{|ch|
      if $DEBUG
        print_map
        print "command=\"#{ch}\"\n"
        print "pos=(#{@point[0]},#{@point[1]})  " +
          "direction=(#{@direction[0]},#{@direction[1]})  " +
          "result=[#{@result}]\n"
        print "stack=#{@stack.inspect}\n"
        print "\n\n\n"
      end

      if @str_mode
        if ch == "\""
          @str_mode = false
        else
          push ch[0]
        end
      elsif @skip
        @skip = false
      elsif ch =~ /\d/
        push ch.to_i
      else
        @cmd[ch].call(ch)
      end
    }
  end

  def print_map
    @map.each do |line|
      line.each do |ch|
        print ch
      end
      print "\n"
    end
  end
end

b = Befunge.new

b.eval ARGF.read

ループとHelloWorld以外で確認したプログラムは以下です。

5の階乗を求める

5 100p:v
v *g00:_00g.@
>00p1-:^"


ここののっているプログラムで使っていないコマンドはテストしていないからうまく動かないかも。