brainfuck-dialect quine generator

それでも @mametter ならっ・・・! RT @bonotake: quineの作りがいがありそうな…作んないけど RT @masahiro_sakai: ジョジョ言語わろた http://bit.ly/d0RUUR

http://twitter.com/masahiro_sakai/status/9091442089

brainfuck の命令文字を置換しただけの俺言語は結構ある *1 ので、いちいち quine を作るのは面白くありません。そこで、命令文字の割り当てから、その言語での quine を自動生成するソリューションを開発しました。

brainfuck の quine の生成。

$ ruby19 bfd-quine-gen.rb bf > quine.bf
$ beef quine.bf > quine2.bf
$ diff quine.bf quine2.bf

たぶん一番有名な置換言語 Ook! での quine 。生成物

$ ruby19 bfd-quine-gen.rb ook > quine.ook
$ java Ook quine.ook quine.bf
$ beef quine.bf > quine2.ook
$ diff quine.ook quine2.ook

ジョジョ言語 バージョン3 での quine *2生成物

$ ruby19 bfd-quine-gen.rb jojo > quine.jojo
$ ruby jojo.rb quine.jojo > quine2.jojo
$ diff quine.jojo quine2.jojo

1.8 だと実行に 3 分くらいかかりました。1.9 だと 1 分くらい *3 。1.9 は素晴らしい。

以下ソース。

# coding: UTF-8

case ARGV.first
when "bf"
  INSN_TABLE = {
    "<" => "<", ">" => ">",
    "+" => "+", "-" => "-",
    "[" => "[", "]" => "]",
    "." => ".",
  }

when "ook"
  INSN_TABLE = {
    "<" => "Ook? Ook. ", ">" => "Ook. Ook? ",
    "+" => "Ook. Ook. ", "-" => "Ook! Ook! ",
    "[" => "Ook! Ook? ", "]" => "Ook? Ook! ",
    "." => "Ook! Ook. ",
  }

when "jojo"
  $JOJOBUG = "+-"
  INSN_TABLE = {
    "<" => "ロードローラ",
    ">" => "スターフィンガ",
    "+" => "オラ",
    "-" => "無駄",
    "[" => "あ・・・ありのまま今起こったことを話すぜ",
    "]" => "ザ・ワールド",
    "." => "ハーミットパープル",
  }
end

def adjust(p1, p2, c1, c2)
  p1 < p2 ? c1 * (p2 - p1) : c2 * (p1 - p2)
end

def gen_code(assign)
  code = <<-END.gsub(/#.*|\s/, "")
    # load DATA (run length compressed)
    #>>>(len)>(char)>(len)>(char)>...

    # outputs code that outputs DATA
    >#$JOJOBUG<[<]out(1,#$JOJOBUG>>#$JOJOBUG)>[<out(1,>)>[-<out(1,+)<+>>]>]<<<

    # outputs code decoded from DATA
    [<]>[>
    -[-[-[-[-[-[-
     <[-<out(2,#{ assign[6] })>]>
    ]<[-<out(2,#{ assign[5] })>]>
    ]<[-<out(2,#{ assign[4] })>]>
    ]<[-<out(2,#{ assign[3] })>]>
    ]<[-<out(2,#{ assign[2] })>]>
    ]<[-<out(2,#{ assign[1] })>]>
    ]<[-<out(2,#{ assign[0] })>]>
    >]++++++++++.
  END
  nil while code.gsub!("><", "")
  code.gsub!(/out\((\d+),([<>+\-\[\]\.]+)\)/) do
    pos, buffs, str = 0, [0] * $1.to_i, ""
    $2.each_char do |insn|
      INSN_TABLE[insn].each_byte do |byte|
        diffs = buffs.map.with_index do |buff, idx|
          [idx, adjust(pos, idx, "<", ">") + adjust(buff, byte, "+", "-")]
        end
        pos, s = diffs.min_by {|idx, s| s.size }
        buffs[pos] = byte
        str << s << "."
      end
    end
    buffs.each_with_index.reverse_each do |buff, idx|
      next if buff == 0
      str << adjust(pos, idx, "<", ">") << "[-]"
      pos = idx
    end
    str << adjust(pos, 0, "<", ">")
  end
end

code = gen_code(%w(+ - . > [ < ]))
h = Hash.new(0)
code.scan(/(.)\1*/) {|insn| h[insn] += 1 }
assign = h.sort_by {|k, v| -v }.map {|k, v| k.first }
code = gen_code(assign)
data = code.gsub(/(.)\1*/) { ">#{ "+" * $&.size }>+#{ "+" * assign.index($1) }" }
code = "#$JOJOBUG>>#$JOJOBUG" + data + code
puts code.gsub(/./) { INSN_TABLE[$&] }

*1:英語圏日本語圏yhara さんの本では置換言語の作り方が解説されている。

*2:参照実装では「やれやれだぜ」が動かないとか、(ループ命令のときだけ) セルが 0 初期化扱いにならないとか、バグがあるようです。quine 生成側で吸収しておきました (コード中の $JOJOBUG) 。

*3:$KCODE の代わりに magic comment を書くだけで動いた。