以前學編譯的都喜歡用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解釋器。真的很丑??