Racc で Brainf*ck
そして Brainf*ck、「奇妙な言語」p.46 数値データ格納用のテープの存在(先頭位置 0 から始まる不定長一次元配列)とテープ上の位置を示すポインタを想定した上で
- 「>」ポインタを右に一つ動かす
- 「<」ポインタを左に一つ動かす
- 「+」ポインタ位置の数値を一つ増やす
- 「-」ポインタ位置の数値を一つ減らす
- 「.」ポインタ位置の数値を ASCIIコードと見做してその文字を出力
- 「,」標準入力から一文字読み込みその文字の ASCIIコードをポインタ位置に書き込む
- 「[」ポインタ位置の数値が 0 以外なら何もしない、0 なら対応する「]」まで行き、その「]」の次からまた始める
- 「]」ポインタ位置の数値が 0 なら何もしない、0 以外なら対応する「[」まで戻り、その「[」の次からまた始める
取り敢えず、今までと同じ方針で「[」「]」(制御構造、ジャンプ)なしのもの。
class BrainF_ckParser rule# class BrainF_ckParser expression : | expression TERM { send val[1] } end # class BrainF_ckParser ---- inner def initialize(source) @source = source @tape = [] @pointer = 0 end # def initialize(source) def scan @source.each_char do |c| case c when '>' then yield [:TERM, :right] when '<' then yield [:TERM, :left] when '+' then yield [:TERM, :plus] when '-' then yield [:TERM, :minus] when '.' then yield [:TERM, :put] when ',' then yield [:TERM, :get] end # case c end # @source.each_char do |c| yield nil end # def scan def parse(opts = nil) @yydebug = opts.yydebug if opts.respond_to? :yydebug yyparse self, :scan end # def parse(opts = nil) private def right @pointer += 1 end # def right def left @pointer -= 1 end # def left def plus @tape[@pointer] = (@tape[@pointer] ? @tape[@pointer] : 0) + 1 end # def plus def minus @tape[@pointer] = (@tape[@pointer] ? @tape[@pointer] : 0) - 1 end # def minus def put print (@tape[@pointer] ? @tape[@pointer] : 0).chr end # def put def get c = $stdin.getc @tape[@pointer] = c.respond_to?(:ord) ? c.ord : c end # def get ---- header # -*- coding: utf-8; -*- Version = '$Id: brainf_ck_without_bracket.y 3298 2009-03-07 02:50:25Z hs9587 $' # $Date: 2009-03-07 11:50:25 +0900 (土, 07 3 2009) $ ---- footer require 'optparse' require 'ostruct' opts = OpenStruct.new opts.yydebug = false ARGV.options do |opt| opt.on('--[no-]yydebug', 'debug mode (from racc -t) on/off'){ |v| opts.yydebug = v } opt.release = 'start' opt.banner += "\n\tBrainf*ck interpreter.\nOptions:" opt.parse! end # ARGV.options do |opt| BrainF_ckParser.new((ARGV.size > 0 ? ARGF : $stdin).read).parse(opts)
今までと同じように全部その場で実行する。テープとポインター位置はインスタンス変数に持ってる、ソースを initilarize する時同時に初期化。scan で六つの命令を case分けする、記号の種類は一つだけにして、メソッド名を value に持たせることで処理を振り分ける。ポインタがテープの先頭を越えて左に行くときとか、値の範囲チェックとかしていない、真面目に作るならその辺もきちんとしないといけない(「奇妙な言語」pp.47, 55 参照)。
さて、これに条件付きジャンプ命令を追加しようとすると、全部その場で実行してそれを忘れてしまうという今の方針ではなかなか上手くいかない。素直に構文木を作りましょう。