我想用我刚刚创建的编程语言构建一个Brainfuck(该死的名字)解释器来证明它是图灵完整性。
现在,到目前为止一切都很清楚(<>+-,.
) - 除了一件事:循环([]
)。
我假设你从这里知道(极其困难的)BF语法:
伪代码怎么样?当解释器到达循环开始([
)或循环结束(]
)时,我该怎么办?
检查循环是否应该继续或停止不是问题(current cell==0
),而是:
由于循环可以嵌套,我想我不能只使用包含当前循环起始位置的变量。
我已经看到用各种语言实现的非常小的BF解释器,我想知道他们是如何设法让循环工作但却无法弄明白。
答案 0 :(得分:8)
当您到达[
时,您将测试数据指针。
如果错误,您可以扫描下一个匹配的 ]
字符,计算您看到的[
个]
,并确保将其标记为关闭]
。
如果确实如此,您需要跟踪其位置,以便稍后可以跳回到它。我建议使用堆栈。将当前程序位置推入堆栈,然后当到达{{1}}时,测试数据指针。如果是,请转到堆栈上最顶层的程序位置。如果它是假的,则将该位置从堆栈中弹出并继续。
当您嵌入内部循环时,堆栈将干净地记录每个循环的上下文。
见stack (wikipedia)。这类似于汇编程序处理函数调用的方式。
答案 1 :(得分:5)
这是我的“优化”解释器版本,预先编译循环跳转。
def interpret2(code):
data = [0] * 5000 # data memory
cp = 0 # code pointer
dp = 0 # data pointer
# pre-compile a jump table
stack = []
jump = [None] * len(code)
for i,o in enumerate(code):
if o=='[':
stack.append(i)
elif o==']':
jump[i] = stack.pop()
jump[jump[i]] = i
# execute
while cp < len(code):
cmd = code[cp]
if cmd == '>': dp += 1
elif cmd == '<': dp -= 1
elif cmd == '+': data[dp] += 1
elif cmd == '-': data[dp] -= 1
elif cmd == '.': stdout.write(chr(data[dp]))
elif cmd == ',': data[dp] = ord(stdin.read(1))
elif cmd == '[' and not data[dp]: # skip loop if ==0
cp = jump[cp]
elif cmd == ']' and data[dp]: # loop back if !=0
cp = jump[cp]
cp += 1
执行代码的干运行,跟踪括号(在堆栈中)并在并行jump
数组中标记goto地址,稍后在执行期间查询该数据。
我比较了长时间运行的BF程序的执行速度(计算Pi的N位数),这比无辜的实现提高了2倍,其中源被向前扫描到退出[
并向后扫描到循环在]
。
答案 2 :(得分:1)
如何在我的解释器中实现BF循环?
这就是重点 - 这完全取决于你的语言。对于基于堆栈的编程语言(或任何可以使用堆栈的语言)的情况,@ rjh给出了一个很好的解决方案。其他语言将使用不同的解决方案,例如递归(即隐式使用堆栈)。
答案 3 :(得分:1)
从我的头脑中,可能是一些错误,但这样的事情应该有效:
char* interpret(char* instructions){
char* c = instructions;
while (*c) {
if (*c == ".") putchar(*p);
else if (*c == ",") *p = getchar();
else if (*c == '+') (*p)++;
else if (*c == '-') (*p)--;
else if (*c == '<') p--;
else if (*c == '>') p++;
else if (*c == '[') c = interpret(c+1);
else if (*c == ']') { if (*p) c = instructions else return c; }
c++;
}
return 0;
}
使用brainf * ck源代码调用解释。指针p指向当前存储器位置。在发现[。]时进行递归调用。遇到一个]时从这个递归调用返回。
答案 4 :(得分:0)
我更喜欢使用跳转表(以及将原始BF转换为'字节码')。优化BF解释器显然是要走的路!