首先介绍一下brainfuck的语法:
该语言定义在这样一个环境之上:
你有一列无限长的小火车,每个车厢里装了一个数字,初始为0。
还有一个列车员,初始在最头上那节车厢上。
好了,你把你写的BrainFK程序交给列车员,列车员会做如下的事情:
从左向右、由上自下一个字符一个字符地读取你的程序
当读到+
的时候,将所在车厢里的数字加一
当读到-
的时候,将所在的车厢里的数字减一
当读到>
的时候,跑到后一个车厢去
当读到<
的时候,跑到前一个车厢去
当读到[
的时候,如果该车厢里面的数字为0,则跳去执行下一个]
之后的程序内容
当读到]
的时候,如果该车想里面的数字不为0,则跳去执行上一个[
之后的程序内容
当读到.
的时候,将所在车厢里面的数字翻译成ASCII字符,显示在你的屏幕上
当读到,
的时候,从等待使用者输入一个ASCII字符,转码成数字写进所在车厢里
可以把火车想象成一个纸带,那么解释器需要有一个纸带类。纸带里面需要记录列车员的位置。那么代码如下:
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
|
class Tape(object):
"""
纸带
"""
def __init__(self):
self.tape = [0]
self.position = 0
def get(self):
return self.tape[self.position]
def set(self, val):
self.tape[self.position] = val
def inc(self):
self.tape[self.position] += 1
def dec(self):
self.tape[self.position] -= 1
def forward(self):
self.position += 1
if len(self.tape) <= self.position:
self.tape.append(0)
def backward(self):
self.position -= 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
|
class BrainFuck(object):
def __init__(self,program,tape_obj):
self.program = program # program that you're going to interpret
self.pairs = {}
self.record()
self.tape = tape_obj
def record(self):
"""遍历一次代码,记录'['和']'的相对位置"""
left_stack = []
for i,p in enumerate(self.program):
if p == '[':
left_stack.append(i)
if p == ']':
left = left_stack.pop()
right = i
self.pairs[left] = right
self.pairs[right] = left
def parse(self):
values = []
pc = 0
while pc < len(self.program):
p = self.program[pc]
if p == '+':
self.tape.inc()
elif p == '-':
self.tape.dec()
elif p == '>':
self.tape.forward()
elif p == '<':
self.tape.backward()
elif p == '[':
if self.tape.get() == 0:
pc = self.pairs[pc] # 到下一个]所在的地方
elif p == ']':
if self.tape.get() != 0:
pc = self.pairs[pc]
elif p == '.':
values.append(chr(self.tape.get()))
elif p == ',':
self.tape.set(input())
pc += 1
return ''.join(values)
|
那么它的Hello world!该怎么写呢?为了可读性和程序的长度可控,我们可以这样,用四个车厢来分别控制输出大写字母,小写字母,特殊符号和控制符号。(如果需要输出数字的话,我们可以增加输出数字的车厢。)
由Ascii码表可得知,A的十进制表示为65,a的十进制表示为97,空格和感叹号分别为32,33,换行键是10.为了便于循环,可把四个车厢设置为70,100,30,10。那么尝试写程序如下:
++++++++++[>+++++++>++++++++++>+++>+]++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.-------.--------.>+.>.
假设有个注释符是这个//,那么把程序展开并注释如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
++++++++++ //第0个车厢加到10,次车厢用于定位以及定义循环长度
[
>+++++++ //第1个车厢加到7
>++++++++++ //第2个车厢加到10
>+++ //第3个车厢加到3
>+ //第4个车厢加到1
]
//循环结束时,第1个到第4个车厢数字分别为70,100,30,10
++. //输出H
>+. //输出e
+++++++. //输出l
. //输出l
+++. //输出o
>++. //输出空格
<<+++++++++++++++. //输出W
>. //输出o
+++. //输出r
-------. //输出l
--------. //输出d
>+. //输出!
>. //输出换行键
|
可以看到,这门语言的语法十分简单而且优雅,而且这是一门图灵完备语言,这意味着任何用其他语言写出的程序都可以用它来实现一遍。例如上面用python写成的brainfuck解释器,就可以用brainfuck再写一遍:即用brainfuck写的brainfuck解释器。但是这里纸张太短,写不下了。