仕事でRubyの資格取得目指して勉強中なので、復習ついで?にBrainf*ckのインタプリタを作ってみた。
Wikipediaで仕様を読んで、サンプル2つほど(helloworld、ハノイの塔)でデバッグ。
ちなみにWikipediaのBrainf*ckページにはRubyインタプリタへのリンク(しかもURLを見ると今年の5/14と新しい)があるけど、あえて全く見ないでフルスクラッチで書いた。
module Brainfuck
class BFEngine
def initialize
@bfcodearray = []
@bfcodestr = ""
@ip = 0
@dp = 0
@bfdataarray = [0] * 30000
@datalength = 30000
@bracketlevel = 0
return true
end
def setscript scr
if scr.class != String
return false
end
if scr.count("[") != scr.count("]")
return false
end
@bfcodestr = scr
@bfcodearray = scr.split("")
@ip = 0
return true
end
def exec(start = -1,step = false)
if @bfcodearray.length < 1
return false
end
if start >= 0
@ip = start
end
while @ip < @bfcodearray.length
op = @bfcodearray[@ip]
step ? (print "code<#{@ip}> ") : nil
case op
when "<"
step ? (puts "exec #{op}") : nil
self.l
when ">"
step ? (puts "exec #{op}") : nil
self.r
when "+"
step ? (puts "exec #{op}") : nil
self.i
when "-"
step ? (puts "exec #{op}") : nil
self.d
when "["
step ? (puts "exec #{op}") : nil
self.s
when "]"
step ? (puts "exec #{op}") : nil
self.e
when ","
step ? (puts "exec #{op}") : nil
self.g
when "."
step ? (puts "exec #{op}") : nil
self.p
else
end
@ip += 1
if step then
print "mem<#{@dp}> "
Kernel::p @bfdataarray[0,20]
STDIN.gets
end
end
puts ""
puts "END exec."
begin
while true
STDIN.readchar
end
rescue EOFError
end
true
end
def clearscript
@bfcodestr = ""
@bfcodearray.clear
@ip = 0
return true
end
def cleardata
@bfdataarray.fill(0)
@dp = 0
return true
end
def l
@dp -= 1
if @dp < 0
@dp = 0
end
return @dp
end
def r
@dp += 1
if @dp >= @bfdataarray.length
@bfdataarray.push(0)
end
return @dp
end
def i
@bfdataarray[@dp] += 1
if @bfdataarray[@dp] > 255
@bfdataarray[@dp] = 0
end
return @bfdataarray[@dp]
end
def d
@bfdataarray[@dp] -= 1
if @bfdataarray[@dp] < 0
@bfdataarray[@dp] = 255
end
return @bfdataarray[@dp]
end
def s
if @bfcodearray.length < 1
return false
end
if @bfdataarray[@dp] != 0
return true
end
@bracketlevel = 1
@ip += 1
while @bracketlevel > 0
if @bfcodearray[@ip] == "["
@bracketlevel += 1
end
if @bfcodearray[@ip] == "]"
@bracketlevel -= 1
end
@ip += 1
end
@ip -= 1
return @ip
end
def e
if @bfcodearray.length < 1
return false
end
if @bfdataarray[@dp] == 0
return true
end
@bracketlevel = 1
@ip -= 1
while @bracketlevel > 0
if @bfcodearray[@ip] == "]"
@bracketlevel += 1
end
if @bfcodearray[@ip] == "["
@bracketlevel -= 1
end
@ip -= 1
end
@ip += 1
return @ip
end
def g
c = nil
begin
begin
c = STDIN.readchar
rescue EOFError
retry
end
rescue EOFError
end
if c.ord < 0 || c.ord > 255
return false
end
@bfdataarray[@dp] = c.ord
return c.ord
end
def p
print @bfdataarray[@dp].chr
return @bfdataarray[@dp].chr
end
end
end
まあ、いろいろと突っ込みどころ満載だとは思うけど、とりあえずシンプル(?)に。
もっと最適化とかコード短縮の余地はあると思う。
メモリはunsigned char*相当になるようにしてあるので、0に-すると255、255に+すると0。
使い方としては、上記のコードを「brainfuck.rb」としてカレントディレクトリに保存し、ローカル変数sにBrainf*ckのコードを文字列で読み込んでおき、irbのコンソールから
irb(main):xxx:0> load "brainfuck.rb" ; bfe = Brainfuck::BFEngine.new ; bfe.setscript(s);bfe.exec
と実行するとBrainf*ckコードが実行される。
ちなみにBrainfuck::BFEngine#execの第1引数に開始IP(命令ポインタ、デフォルト0)、第2引数にステップ実行フラグ(デフォルトfalse)を指定できる。
さらに、[と]以外のコードはRuby上から直接インタラクティブ実行できて、
irb(main):xxx:0> bfe.cleardata ; "A".ord.times { bfe.i } ; 26.times { bfe.p ; bfe.i } ; nil
ABCDEFGHIJKLMNOPQRSTUVWXYZ=> nil
のような結果が得られる。
…とまあ、いろいろ書いたけど、実際のところ実用性なんてないよ。
ただちょっとコード書きたい気分だっただけ。もう0時近くなったし、明日は叛逆3回目鑑賞に行くからもうお風呂入って寝る。
コメント