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 - Python

ひねり無し。
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()))

これまた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

こんなもの dict にするのがいいに決まっている、と思ったら Dan さんに先こされているし。
仕方がないので、インデント情報も dict に突っ込んだやつを。
 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
from __future__ import with_statement
import sys

def brainfuck_compile(source):
    try:
        from cStringIO import StringIO
    except:
        from StringIO import StringIO

    actions = {
        '>': ('pointer += 1', 0),
        '<': ('pointer -= 1', 0),
        '+': ('tape[pointer] = tape.get(pointer, 0) + 1', 0),
        '-': ('tape[pointer] = tape.get(pointer, 0) - 1', 0),
        '.': ('sys.stdout.write(chr(tape.get(pointer, 0)))', 0),
        ',': ('tape[pointer] = sys.stdin.read(1)', 0),
        '[': ('while tape.get(pointer, 0):', 1),
        ']': ('', -1),
    }

    generated = StringIO()
    print >>generated, 'import sys'
    print >>generated, 'tape, pointer = dict(), 0'
    indent = 0
    for c in source:
        if c.isspace(): continue
        stmt, indent_diff = actions[c]
        print >>generated, '%*s%s' % (indent, '', stmt)
        indent += indent_diff

    return generated.getvalue()

def iterchar(fp):
    for line in fp:
        for c in line: yield c

def main(args):
    if args:
        for arg in args:
            with file(arg) as fp:
                code = brainfuck_compile(iterchar(fp))
                print code
    else:
        code = brainfuck_compile(iterchar(sys.stdin))
        print code

if __name__ == '__main__':
    main(sys.argv[1:])

コードを張り忘れたので。
 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

Index

Feed

Other

Link

Pathtraq

loading...