以前学编译的都喜欢用Lisp练手,现在貌似改Brainfuck(参见:维基、Wiki)了。
Brainfuck更简单(就八个命令),而且还是Turing完备的。Brainfuck简直就是对Turing机的直接模拟:一条初始为零的带子、一个指针。不过,Turing机的纸带似乎具有代码和数据双重功能,而Brainfuck更像是把纸带当容器)。
Brainfuck的语法如下:
指令 | 动作 |
---|---|
> | 右移一格 |
< | 左移一格 |
+ | 指针指向的字节的值加一 |
- | 指针指向的字节的值减一 |
. | 输出指针指向的单元内容(ASCII码) |
, | 输入内容到指针指向的单元(ASCII码) |
[ | 向後跳转到对应的] |
] | 向前跳转到对应的[指令的次一指令处,如果指针指向的字节非零 |
前六条都还好理解,最后两条就不那么直观了(Turing机里是没有直接对应的)。
def mainloop(program, bracket_map):
pc = 0
tape = Tape()
while pc < len(program):
code = program[pc]
if code == ">":
tape.advance()
elif code == "<":
tape.devance()
elif code == "+":
tape.inc()
elif code == "-":
tape.dec()
elif code == ".":
# print
os.write(1, chr(tape.get()))
elif code == ",":
# read from stdin
tape.set(ord(os.read(0, 1)[0]))
elif code == "[" and tape.get() == 0:
# Skip forward to the matching ]
pc = bracket_map[pc]
elif code == "]" and tape.get() != 0:
# Skip back to the matching [
pc = bracket_map[pc]
pc += 1
pc就是指针(program counter)。
纸带:
class Tape(object):
def __init__(self):
self.thetape = [0]
self.position = 0
def get(self):
return self.thetape[self.position]
def set(self, val):
self.thetape[self.position] = val
def inc(self):
self.thetape[self.position] += 1
def dec(self):
self.thetape[self.position] -= 1
def advance(self):
self.position += 1
if len(self.thetape) <= self.position:
self.thetape.append(0)
def devance(self):
self.position -= 1
这就体现出高阶语言的好处了:不用考虑分配内存,近乎无限的纸带。
def parse(program):
parsed = []
bracket_map = {}
leftstack = []
pc = 0
for char in program:
if char in ('[', ']', '<', '>', '+', '-', ',', '.'):
parsed.append(char)
if char == '[':
leftstack.append(pc)
elif char == ']':
left = leftstack.pop()
right = pc
bracket_map[left] = right
bracket_map[right] = left
pc += 1
return "".join(parsed), bracket_map
def run(fp):
program_contents = ""
while True:
read = os.read(fp, 4096)
if len(read) == 0:
break
program_contents += read
os.close(fp)
program, bm = parse(program_contents)
mainloop(program, bm)
run(os.open("/Users/Pope/Desktop/test.bf", os.O_RDONLY, 0777))
test.bf 的内容:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
Wiki 上有解释:
+++++ +++++ initialize counter (cell #0) to 10
[ use loop to set the **next** four cells to 70/100/30/10
> +++++ ++ add 7 to cell #1
> +++++ +++++ add 10 to cell #2
> +++ add 3 to cell #3
> + add 1 to cell #4
<<<< - decrement counter (cell #0)
]
> ++ . print 'H'
> + . print 'e'
+++++ ++ . print 'l'
. print 'l'
+++ . print 'o'
> ++ . print ' '
<< +++++ +++++ +++++ . print 'W'
> . print 'o'
+++ . print 'r'
----- - . print 'l'
----- --- . print 'd'
> + . print '!'
> . print '/n'
P.S. 本文代码源自一篇介绍如何用PyPy写解释器(已被墙)的文章,不过前半部分与PyPy没什么关系。
P.P.S 还好一般不译语言的名字,不然Brainfuck是该译成“脑残”好呢,还是“脑袋被驴踢了”。
P.P.P.S 本着“Python能干的事情Ruby也能做”的理念,我用Ruby改写了一遍:
def mainloop(program, bracket_map)
pc = 0
tape = Tape.new
while pc < program.length
code = program[pc]
case code
when ">"
tape.advance()
when "<"
tape.devance()
when "+"
tape.inc()
when "-"
tape.dec()
when "."
print(tape.get().chr)
when ","
tape.set(gets())
when "["
pc = bracket_map[pc] if tape.get == 0
when "]"
pc = bracket_map[pc] if tape.get != 0
else
# do nothing
end
pc += 1
end
end
class Tape
def initialize
@the_tape = [0]
@position = 0
end
def get; @the_tape[@position]; end
def set(val); @the_tape[@position] = val; end
def inc; @the_tape[@position] += 1; end
def dec; @the_tape[@position] -= 1; end
def advance
@position += 1
@the_tape.push(0) if @position >= @the_tape.length
end
def devance; @position -= 1; end
end
def parse(program)
parsed = []
bracket_map = {}
leftstack = []
pc = 0
program.each_char do |char|
if ['[', ']', '<', '>', '+', '-', ',', '.'].include? char
parsed.push(char)
if char == '['
leftstack.push(pc)
elsif char == ']'
left = leftstack.pop
right = pc
bracket_map[left] = right
bracket_map[right] = left
end
pc += 1
end
end
[parsed, bracket_map]
end
def run(fp)
program_contents = IO.read(fp)
program, bm = parse(program_contents)
mainloop(program, bm)
end
run("/Users/Pope/Desktop/test.bf")
P.P.P.P.S 有人写过一个很丑但很短的Ruby版Brainfuck解释器。真的很丑