Racc で Brainf*ck

そして Brainf*ck、「奇妙な言語」p.46 数値データ格納用のテープの存在(先頭位置 0 から始まる不定長一次元配列)とテープ上の位置を示すポインタを想定した上で

  1. 「>」ポインタを右に一つ動かす
  2. 「<」ポインタを左に一つ動かす
  3. 「+」ポインタ位置の数値を一つ増やす
  4. 「-」ポインタ位置の数値を一つ減らす
  5. 「.」ポインタ位置の数値を ASCIIコードと見做してその文字を出力
  6. 「,」標準入力から一文字読み込みその文字の ASCIIコードをポインタ位置に書き込む
  7. 「[」ポインタ位置の数値が 0 以外なら何もしない、0 なら対応する「]」まで行き、その「]」の次からまた始める
  8. 「]」ポインタ位置の数値が 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 参照)。

さて、これに条件付きジャンプ命令を追加しようとすると、全部その場で実行してそれを忘れてしまうという今の方針ではなかなか上手くいかない。素直に構文木を作りましょう。