challenge BFコンパイラー

「どう書く?」でまだ出ていないのが不思議なお題。それがBF処理系。 ここでは、BFで書かれたソースを、同じ言語に変換するコンパイラーを募集します。

私自身、すでにPerlとJavaScriptに関しては http://blog.livedoor.jp/dankogai/archives/50545151.html でやっているのですが、他の言語バージョンも是非見たいので。

Dan the Brainf.ucker

以下のようにonelinerで可能です。 ただしLanguage::BF 0.03が必要です。 CodeRepos経由 で、

  • svn co svn.coderepos.org/share/lang/perl/Language-BF
  • cd Language-BF/trunk
  • perl Makefile.PL
  • make install

するか、CPANにVersion 0.03が現れるのをお待ち下さい。

Dan the Brainf.cker

1
2
3
4
perl -MLanguage::BF \
  -e 'print Language::BF->new_from_file(shift)->as_perl' t/hello.bf \
  | perl
Hello World!

Posted feedbacks - Nested

Flatten Hidden
ひさびさの一番かな?

入力を文字列にしたい場合なんかは
Console.withInを使うとできます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import scala.io.Source.fromFile

object BF{
  def main(args:Array[String]) = {
    if(args.size == 0) print("scala BF [sourcecode filename]")
    else
    args.foreach{f=> (new Machine).interpret(fromFile(f).mkString("")) }
  }
}

object Machine {
  final val MEM = 0xffffff
  final val VAL = 0xff
}

class Machine{
  var _p = 0
  val _mem = new Array[int](Machine.MEM)
  def p_=(v:int) = _p = v&Machine.MEM
  def p = _p
  def mem_=(v:int) = _mem(p)=v&Machine.VAL
  def mem = _mem(p)

  def abort = error("Missing corresponding parenthesis.")

  def interpret(code:String):unit = interpret(code.toArray)
  def interpret(code:Array[char]):unit = {
    val (s,m) = ((List[int](), List[(int,int)]()) /: code.zipWithIndex){
      (r,c) => c._1 match {
        case '[' => (c._2::r._1, r._2)
        case ']' => r._1 match {
          case x::xs => (xs, (x,c._2)::(c._2,x)::r._2)
          case _ => abort
        }
        case _   => r
    }}
    if(s.size > 0) abort
    val parenMap = Map(m:_*)

    var i = -1;while({i=i+1;i<code.size}) code(i) match {
      case '>' => p = p+1
      case '<' => p = p-1
      case '+' => mem = mem+1
      case '-' => mem = mem-1
      case '.' => print(mem.asInstanceOf[char])
      case ',' => mem = readChar.asInstanceOf[int]
      case '[' if mem == 0 => i = parenMap(i)
      case ']' if mem != 0 => i = parenMap(i)
      case _   => ()
    }
  }
}
題意を読み間違えてたので。

こっちのほうがかなり楽です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import scala.io.Source.fromFile

object BFC{
  def main(args:Array[String]) = {
    if(args.size == 0){
      println("Usage:scala BFC [sourcecode filename]")
    }else{
      println("""
        object Machine {
          final val MEM = 0xffffff
          final val VAL = 0xff
        }

        class Machine{
          var _p = 0
          val _mem = new Array[int](Machine.MEM)
          def p_=(v:int) = _p = v&Machine.MEM
          def p = _p
          def mem_=(v:int) = _mem(p)=v&Machine.VAL
          def mem = _mem(p)

          def eval = {
      """)
      val code = fromFile(args(0)).mkString("").toList
      if(code.count('['==_) != code.count(']'==_)) {
        error("Missing corresponding parenthesis.")
      }
      code.foreach(c=>println(c match {
        case '>' => "p = p+1"
        case '<' => "p = p-1"
        case '+' => "mem = mem+1"
        case '-' => "mem = mem-1"
        case '.' => "print(mem.asInstanceOf[char])"
        case ',' => "mem = readChar.asInstanceOf[int]"
        case '[' => "while(mem != 0){"
        case ']' => "}"
        case _   => ""
      }))
      println("}};(new Machine).eval;")
    }
  }
}
お題を見て「BF→言語Aへのトランスレータを言語Aで製作する」のだと思ったんですけど、お手本見るとなんかちょっと違う感じがしたので全部やってみました (^-^;; BF ソースファイルから C# ソースと実行ファイルを生成してそれを実行します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.CSharp;
static class BFCompiler {
    public static void Main(String[] args) {
        if (0 < args.Length && File.Exists(args[0])) {
            using(StringWriter sw = new StringWriter())
            using(StreamReader sr = new StreamReader(args[0])) {
                Stack<string> labels = new Stack<string>();
                int label_num = 0;
                sw.WriteLine("using System; static class BF {");
                sw.WriteLine("public static void Main() {");
                sw.WriteLine("byte[] m = new byte[256]; int p = 0;");
                foreach(char c in sr.ReadToEnd()) {
                    switch(c) {
                        case '+': sw.WriteLine("m[p]++;"); break;
                        case '-': sw.WriteLine("m[p]--;"); break;
                        case '>': sw.WriteLine("p++;"); break;
                        case '<': sw.WriteLine("p--;"); break;
                        case '.': sw.WriteLine("Console.Write((char)m[p]);"); break;
                        case ',': sw.WriteLine("m[p] = (byte)Console.Read();"); break;
                        case '[': {
                            string ll = "L" + (label_num++);
                            labels.Push(ll);
                            sw.WriteLine("if (m[p] == 0) goto {0}_END;", ll);
                            sw.WriteLine("{0}_START:;", ll);
                            break;
                        }
                        case ']': {
                            string ll = labels.Pop();
                            sw.WriteLine("if (m[p] != 0) goto {0}_START;", ll);
                            sw.WriteLine("{0}_END:;", ll);
                            break;
                        }
                    }
                }
                sw.WriteLine("}}");
                Generate(Path.GetFileNameWithoutExtension(args[0]), sw.ToString());
            }
        }
        else {
            Console.WriteLine("usage: bfc [sourcefile]");
        }
    }
    private static void Generate(string filename, string cs_code) {
        // ソースファイル生成
        using (StreamWriter sw = new StreamWriter(filename + ".cs")) {
            sw.Write(cs_code);
        }
        // 実行ファイル生成
        CompilerParameters param = new CompilerParameters();
        param.OutputAssembly = filename + ".exe";
        param.GenerateExecutable = true;
        CompilerResults rs = new CSharpCodeProvider().CompileAssemblyFromSource(param, cs_code);
        // 実行
        rs.CompiledAssembly.GetType("BF").GetMethod("Main").Invoke(null, null);
    }
}

>お題を見て「BF→言語Aへのトランスレータを言語Aで製作する」のだと思ったんですけど

それであっていると思いますよ。お手本は 「Language::BF->new_from_file(shift)->as_perl」というコードで「t/hello.bf」というBrainf*ckで書かれたコードを読んでPerlに変換し、その出力をさいごの「| perl」でもう一度Perlに食わせて実行させているわけです。

perl -MLanguage::BF \
-e 'print Language::BF->new_from_file(shift)->as_perl' t/hello.bf \
| perl
Hello World!
SiroKuroさんがおっしゃっているお手本は
http://blog.livedoor.jp/dankogai/archives/50545151.html  

のほうだと思います。投稿時間を見てみるとわかります。

僕もURLの方だけみて勘違いしてました・・・
今から書き直します(^^;
あ、ごめんなさい。yuin さんの仰るとおり、弾さんのブログを見ての疑問でした。弾さんのブログだと普通に実行してるみたいでしたので……。 んで、トランスレートかな?コンパイルかな?実行かな?と迷ったので全部やってみた感じです。どれか1つはあってると思いたい (^-^;;
はっきりしなくてごめんなさい。にしおさんが言う通り。

コンパイラー(トランスレーター)
  BF ====> 言語A
      言語A

というのが本来の趣旨で、そしてBFの場合こちらの方が

インタープリター(ランタイム)
  BF => 言語Aで書かれた実行環境

よりもずっと実装が簡単なので。
ちなみにLanguage::BFはどちらの機能も持っています。

Dan the Brainf.cker
なんか弄ってたら得体の知れないものに……w
1
2
3
4
5
6
7
using System;
using System.IO;
static class BFCompiler {
    public static void Main(String[] args) {
        Console.WriteLine(0 == args.Length || !File.Exists(args[0]) ? "usage: bfc [sourcefile]" : "using System;static class BF{static void Main(){byte[]m=new byte[256];int p=0;" + new StreamReader(args[0]).ReadToEnd().Replace("]","}").Replace("[","while(m[p]!=0){").Replace(".","Console.Write((char)m[p]);").Replace(",","m[p]=(byte)Console.Read();").Replace("+","m[p]++;").Replace("-","m[p]--;").Replace(">","p++;").Replace("<","p--;") + "}}");
    }
}
ひねり無し。
python bf.py -o hello.py hello.bf  みたいな感じで使います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import sys
from getopt import getopt

def encode(bfcode):
    depth = code = 0
    pycode = []
    stack = []
    
    f = file(default['-o'], 'w')
    
    pycode.append('import sys')
    pycode.append('tape, ptr, code = {}, 0, 0')
    
    while code != len(bfcode):
        c = bfcode[code]
        
        if c == '>':
            pycode.append('\t' * depth + 'ptr += 1')
        elif c == '<':
            pycode.append('\t' * depth + 'ptr -= 1')
        elif c == '+':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) + 1')
        elif c == '-':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) - 1')
        elif c == ',':
            pycode.append('\t' * depth + 'tape[ptr] = sys.stdin.read(1)')
        elif c == '.':
            pycode.append('\t' * depth + 'sys.stdout.write(chr(tape.get(ptr, 0)))')
        elif c == '[':
            pycode.append('\t' * depth + 'while tape.get(ptr, 0):')
            stack.append(depth)
            depth += 1
        elif c == ']':
            depth = stack.pop()
        
        code += 1
    
    file(default['-o'], 'w').write('\n'.join(pycode))


if __name__ == '__main__':
    default = {'-o': 'a.py'}
    optlist, args = getopt(sys.argv[1:], 'o:', [])
    if args:
        default.update(optlist)
        encode(''.join(file(args[0]).readlines()))
自己ツッコミ。
このままだと"~[]~" のようなBFコードがあった際にwhile文のところで文法エラーを起こすので、
']'が出た時にはpassをダミーで放り込んだ方がよさそうです。


あと、最初間違えてインタプリタを書いてしまった名残で変なやり方になっていました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def encode(bfcode):
-   depth = code = 0 
+   depth = 0
    pycode = []
    stack = []
    
    f = file(default['-o'], 'w')
    
    pycode.append('import sys')
-   pycode.append('tape, ptr, code = {}, 0, 0')
+   pycode.append('tape, ptr = {}, 0')
    

-     while code != len(bfcode):
-        c = bfcode[code]
+     for c in bfcode:
        if c == '>':
            pycode.append('\t' * depth + 'ptr += 1')
        elif c == '<':
            pycode.append('\t' * depth + 'ptr -= 1')
        elif c == '+':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) + 1')
        elif c == '-':
            pycode.append('\t' * depth + 'tape[ptr] = tape.get(ptr, 0) - 1')
        elif c == ',':
            pycode.append('\t' * depth + 'tape[ptr] = sys.stdin.read(1)')
        elif c == '.':
            pycode.append('\t' * depth + 'sys.stdout.write(chr(tape.get(ptr, 0)))')
        elif c == '[':
            pycode.append('\t' * depth + 'while tape.get(ptr, 0):')
            stack.append(depth)
            depth += 1
        elif c == ']':
+           pycode.append('\t' * depth + 'pass')
            depth = stack.pop()

-    code += 1

これまたifの羅列をHashに置き換え。ただし、pythonの場合、[]は少し特別扱いが必要。こちらはindent不要とは行かないので。 あと、関数名やインターフェースも好みにあわせて変えました。

Dan the Novice Snake Tamer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python
import sys
from getopt import getopt

def bf2py(bfcode):
    depth = 0
    opcode = {
        '>':'ptr += 1',
        '<':'ptr -= 1',
        '+':'tape[ptr] = tape.get(ptr, 0) + 1',
        '-':'tape[ptr] = tape.get(ptr, 0) - 1',
        ',':'tape[ptr] = sys.stdin.read(1)',
        '.':'sys.stdout.write(chr(tape.get(ptr, 0)))',
        '[':'while tape.get(ptr, 0):',
        ']':'pass'
    }
    pycode = []
    stack = []
    
    pycode.append('import sys')
    pycode.append('tape, ptr = {}, 0')
    
    for c in bfcode:
        if opcode.has_key(c):
            pycode.append( '\t' * depth + opcode[c])
            if c == '[':
                stack.append(depth)
                depth += 1
            elif c == ']':
                depth = stack.pop()
     
    return '\n'.join(pycode)

if __name__ == '__main__':
    defout = 'a.py'
    optlist, args = getopt(sys.argv[1:], 'o:', [])
    if args:
        pysrc = bf2py(''.join(file(args[0]).readlines()));
        file(defout or ops['-o'], 'w').write(pysrc)

せっかくなので++++がbf.inc(4)に置き換わるような設計にしてみました。

この程度の規模なら正規表現でも十分いけるんじゃないかと思いつつ勉強がてらにlexとyaccを使いました。

PLY (Python Lex-Yacc)

あとインデントうんぬんを考慮しないといけないのはそもそも直接while文を使うからなので式で表現しました。

,+[-.,+]を入力するとbf.get()or bf.inc(1)or bf.loop(lambda: bf.inc(-1)or bf.put()or bf.get()or bf.inc (1))と出力されます。

下のFizzBuzzコードを食わせると2638バイトの出力で、処理時間はあっという間でした。 http://d.hatena.ne.jp/n_shuyo/20070516/fizzbuzz

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from ply import lex, yacc
import sys

# lex
tokens = "PLUS MINUS LEFT RIGHT WHILE WEND PUT GET COMMENT".split()

t_WHILE = "\["
t_WEND = "]"
t_PUT = "\."
t_GET = ","

def t_PLUS(t):
    "\++"
    t.value = len(t.value)
    return t

def t_MINUS(t):
    "-+"
    t.value = len(t.value)
    return t

def t_LEFT(t):
    "<+"
    t.value = len(t.value)
    return t

def t_RIGHT(t):
    ">+"
    t.value = len(t.value)
    return t

def t_COMMENT(t):
    '[^[\]><+-,.]' # return nothing

def t_error(t): pass
    
# yacc
"""*** grammar definition
sequence : sequence command
         | command

command : PLUS
        | MINUS
        | LEFT
        | RIGHT
        | PUT
        | GET
        | loop

loop : WHILE sequence WEND
"""

def p_sequence(p):
    "sequence : sequence command"
    p[0] = p[1] + "or " + p[2]

def p_sequence_command(p):
    "sequence : command"
    p[0] = p[1]

def p_command_PLUS(p):
    "command : PLUS"
    p[0] = "bf.inc(%s)" % p[1]

def p_command_MINUS(p):
    "command : MINUS"
    p[0] = "bf.inc(%s)" % -p[1]

def p_command_LEFT(p):
    "command : LEFT"
    p[0] = "bf.mov(%s)" % -p[1]

def p_command_RIGHT(p):
    "command : RIGHT"
    p[0] = "bf.mov(%s)" % p[1]

def p_command_PUT(p):
    "command : PUT"
    p[0] = "bf.put()"

def p_command_GET(p):
    "command : GET"
    p[0] = "bf.get()"

def p_command_loop(p):
    "command : loop"
    p[0] = p[1]
    
def p_loop(p):
    "loop : WHILE sequence WEND"
    p[0] = "bf.loop(lambda: %s)" % p[2]

def p_error(p): pass

# input and parse
data = sys.stdin.read()
lex.lex()
yacc.yacc()
result = yacc.parse(data)

# output
print """
from collections import defaultdict
import sys
class BF(object):
    mem = defaultdict(int)
    cur = 0
    def inc(self, n):
        self.mem[self.cur] += n
        self.mem[self.cur] %= 256
    def mov(self, n):
        self.cur += n
    def put(self):
        c = chr(self.mem[self.cur])
        sys.stdout.write(c)
    def get(self):
        c = sys.stdin.read(1)
        self.mem[self.cur] = ord(c)
    def loop(self, seq):
        while self.mem[self.cur]:
            seq()

bf = BF()
"""

print result

素数探索のコード:

http://labs.cybozu.co.jp/blog/kazuho/archives/2006/06/bf_prime.php

さすがに結構時間がかかるなー。Core2Duo @2.4GHzで1分くらい。

real    1m2.317s
user    0m0.015s
sys     0m0.031s

8行修正して[-]を単体で「リセット命令」にしたらreal 0m54.380sになった。

うーん、これ以上高速化するとなるとコピーの処理を置き換えるとかになりそうだけど、それは正規表現では無理な気がするなぁ。パースしながら出力文字列を作るのをやめて、sequenceを文字列に変換する際にmovとincだけで構成されているかどうかをチェックして…となるのかな。面倒だな。

Pythonだとこれだけ時間がかかるんですね。
参考になります。

Scalaでもやってみたところ、#3960のコードで
出力したものを、コンパイルせずインタプリタで
実行しても1秒以内に結果が出ました。
Scalaは実はできる子です(笑

うーん、それはちょっと違いますね。 #3960のコードはコードを一つの関数の中にローカルに展開しているのに対して、上のコードは一つ一つの命令がメソッド呼び出しですから。PythonとScalaの性能の違いと言うより、生成されたコードの質の違い思います。

# 要するに僕のコードが生成したのは質が悪いと!orz

毎回bf.fooとメソッド名の解決をしているのをやめるとreal:0m32.481sになり、きちんとインデントするようにしたら(毎回関数呼び出しをするのをやめたら)real:0m15.548sになりました。

それでもScalaには全然追いつかないのか…orz

コードを張り忘れたので。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def p_sequence(p):
    "sequence : sequence command"
    p[0] = p[1] + p[2]

def p_sequence_command(p):
    "sequence : command"
    p[0] = p[1]

def p_command_PLUS(p):
    "command : PLUS"
    p[0] = "\nmem[cur] = (mem[cur] + %s) %% 256" % p[1]

def p_command_MINUS(p):
    "command : MINUS"
    p[0] = "\nmem[cur] = (mem[cur] - %s) %% 256" % p[1]

def p_command_LEFT(p):
    "command : LEFT"
    p[0] = "\ncur -= %s" % p[1]

def p_command_RIGHT(p):
    "command : RIGHT"
    p[0] = "\ncur += %s" % p[1]

def p_command_PUT(p):
    "command : PUT"
    p[0] = "\nsys.stdout.write(chr(mem[cur]))"

def p_command_GET(p):
    "command : GET"
    p[0] = "\nmem[cur] = ord(sys.stdin.read(1))"

def p_command_RESET(p):
    "command : RESET"
    p[0] = "\nmem[cur] = 0"

def p_command_loop(p):
    "command : loop"
    p[0] = p[1]
    
def p_loop(p):
    "loop : WHILE sequence WEND"
    p[0] = "\nwhile mem[cur]:%s" % p[2].replace("\n", "\n    ")

def p_error(p): pass

# input and parse
data = sys.stdin.read()
lex.lex()
yacc.yacc()
result = yacc.parse(data)

# output
print """
from collections import defaultdict
import sys
mem = defaultdict(int)
cur = 0
"""

print result
あ、確かにそうですね。見落としてました。

せっかくなので#3952の素朴なタイプでも手元で時間とって見ましたが、

dppさんの#3952:55秒程度
にしおさんの#3988:2分10秒程度

でした。

あと、メモリとしてdictを使っているけど、
[0]*0xffffみたいにリストで長さ決めうちにすると
だいぶマシですね。55 -> 31秒程度まで短縮できました。

これ

tape[ptr] = sys.stdin.read(1)

の部分でordしていないのでテープに文字列が書き込まる気が。

簡単の為、標準出力を使用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>

int main(int arg_c, char **arg_v) {
    FILE *fp;
    int c;

    if (*++arg_v == NULL) {
        fprintf(stderr, "usage: bfc [source file]\n");
        exit(EXIT_FAILURE);
    }
    if ((fp = fopen(*arg_v, "r")) == NULL) {
        fprintf(stderr, "file open error.\n");
        exit(EXIT_FAILURE);
    }
    
    puts("#include <stdio.h>");
    puts("#include <stdlib.h>");
    puts("int main(void) {");
    puts(" char *s, *p;");
    puts(" p = s = (char *)calloc(1024, 1);");
    
    while ((c = getc(fp)) != EOF) {
        switch (c) {
            case '>': puts(" ++p;"); break;
            case '<': puts(" --p;"); break;
            case '+': puts(" ++*p;"); break;
            case '-': puts(" --*p;"); break;
            case '.': puts(" putchar(*p);"); break;
            case ',': puts(" *p = getchar();"); break;
            case '[': puts(" while (*p) {"); break;
            case ']': puts(" }"); break;
            default: break;
        }
    }
    
    puts(" free(s);");
    puts("}");
    
    fclose(fp);
}
出力のインデント処理と配列の長さを引数で調整できるようにしてみました。
./bf2c hello.bf > hello.c
または
./bf2c hello.bf 128 > hello.c
のように使います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>

char *code[] = { "sp++;", "sp--;", "(*sp)++;", "(*sp)--;", "putchar(*sp);",
                 "*sp = getchar();", "while(*sp){", "}" };

int main( int argc, char *argv[] ){
   int c,i,j,n=0;
   int indent = 1;
   int length;
   FILE *fp;

   if( argc < 2 ){
      fprintf(stderr,"Usage: %s sourcefile\n", argv[0] );
      return EXIT_FAILURE;
   }
   if( (fp = fopen( argv[1], "r" )) == NULL ){
      fprintf(stderr,"Error: %s cannot opened\n", argv[1] );
      return EXIT_FAILURE;
   }
   if( !argv[2] || (length = atoi( argv[2] )) <= 0 ){
      length = 256;
   }

   puts("#include <stdio.h>");
   puts("#include <stdlib.h>");
   printf("#define DATA_LEN %d\n", length);
   puts("char code[DATA_LEN];");
   puts("int main (void){");
   puts("   char *sp = code;");
   while( (c=fgetc( fp )) != EOF ){
      switch(c){
      case '>': i = 0; break;
      case '<': i = 1; break;
      case '+': i = 2; break;
      case '-': i = 3; break;
      case '.': i = 4; break;
      case ',': i = 5; break;
      case '[': i = 6; indent++; break;
      case ']': i = 7; indent--; break;
      default:
         /* skip other characters */
         continue;
      }
      for( j = 0; j < (indent+(i==6?-1:0)); j++ ){
         printf("   ");
      }
      printf("%s\n", code[i] );
   }
   puts("   return EXIT_SUCCESS;");
   puts("}");
   fclose(fp);
   return EXIT_SUCCESS;
}

ケースが嫌いな私は、以下のように書き換えてしまいました。機能的には#3955と互換ですが、出力されたCコードのコンパイルには差し支えないのでインデントは省略しました。

Dan the Brainf.cker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char *argv[] ){
    int c;
    char *code[256];
    int length;
    FILE *fp;

    if( argc < 2 ){
        fprintf(stderr,"Usage: %s sourcefile\n", argv[0] );
        return EXIT_FAILURE;
    }
    if( (fp = fopen( argv[1], "r" )) == NULL ){
        fprintf(stderr,"Error: %s cannot opened\n", argv[1] );
        return EXIT_FAILURE;
    }
    if( !