Brainf*ckインタプリタをRubyで実装してみる
実用性ゼロのシンプルな言語Brainf*ckのインタプリタをRubyで実装してみました。Googleで検索すると他の人もいっぱいRubyで実装していますが、まぁそれはそれということで。
Brainf*ckの言語仕様はこちらです。Brainfuck - Wikipedia
このインタプリタの動作確認は、Brainf*ckのサンプルを使いました。
ファイル: bf.rb
#!/usr/bin/env ruby # -*- ruby -*- require 'ostruct' $opts = OpenStruct.new(:debug => 0) class String def each_byte_with_index index = -1 self.each_byte do |byte| index += 1 yield(byte, index) end end end module Enumerable def inject_with_index(init) index = -1 inject(init) do |result, item| index += 1 yield(result, item, index) end end end =begin BrainFuck Language Specification > ... ++ptr; < ... --ptr; + ... ++(*ptr); - ... --(*ptr); . ... putchar(*ptr); , ... *ptr = getchar(); [ ... while(*ptr) { ] ... } =end class BrainFuck DISPATCH_TABLE = { ?> => :increment_pointer, ?< => :decrement_pointer, ?+ => :increment_byte, ?- => :decrement_byte, ?. => :output_byte, ?, => :input_byte, ?[ => :while_begin, ?] => :while_end } EXPATTERN = '^' + DISPATCH_TABLE.keys.map {|c| '\\' + c.chr }.join def initialize(memsize = 64 * 1024) @program = '' @memory = Array.new(memsize, 0) @jump_table = {} @p = 0 @pc = 0 end def run(input) @program = input.read.tr(EXPATTERN, '') parse while @pc < @program.size trace if $opts.debug > 0 send(DISPATCH_TABLE[@program[@pc]]) break if @pc < 0 @pc += 1 end puts # for zsh. end def parse stack = [] @program.each_byte_with_index do |b, i| case DISPATCH_TABLE[b] when :while_begin stack.push i when :while_end pc = stack.pop @jump_table[i] = pc - 1 @jump_table[pc] = i end end end def trace(offset = 0, size = 10) mem = @memory[offset, size].inject_with_index('') do |s, b, i| s + ((i == @p) ? "[%02x]" : " %02x ") % b end printf "%08x:%c: %s\n", @pc, @program[@p], mem end def increment_pointer; @p += 1 end def decrement_pointer; @p -= 1 end def increment_byte; @memory[@p] += 1 end def decrement_byte; @memory[@p] -= 1 end def output_byte print @memory[@p].chr end def input_byte c = $stdin.getc if c @memory[@p] = c else @pc = -1 end end def while_begin @pc = @jump_table[@pc] if @memory[@p] == 0 end def while_end @pc = @jump_table[@pc] end end if __FILE__ == $0 require 'optparse' Version = '0.1a' OptionParser.new do |opt| opt.on('-d', '--debug', 'enable debug mode.') do $opts.debug += 1 end opt.parse!(ARGV) end begin BrainFuck.new.run(ARGF) rescue Interrupt # ignore end end
実行例:
$ cat hello.bf >+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++ ++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]> $ ./bf.rb hello.bf Hello World! $
さて次はWhitespaceかな。Whitespace - Wikipedia