function brainfuck(source) {
var code = source.replace(/[^-+<>.,[\]]/g, '').split(''); // program code
var loop = []; // stack of loops created by bracket operators
var data = []; // array of data cells stored by the program code
var cell = 0; // index in the data array representing one "cell" of data
var next = 0; // index in the code array of the next instruction to run
var operation = {
'>': function () {
if (~loop[0]) {
++cell;
}
},
'<': function () {
if (~loop[0]) {
--cell;
}
},
'+': function () {
if (~loop[0]) {
data[cell] = (data[cell] || 0) + 1;
}
},
'-': function () {
if (~loop[0]) {
data[cell] = (data[cell] || 0) - 1;
}
},
'.': function () {
if (~loop[0]) {
brainfuck.write(data[cell]);
}
},
',': function () {
if (~loop[0]) {
data[cell] = brainfuck.read();
}
},
'[': function () {
loop.unshift(data[cell] ? next : -1);
},
']': function () {
if (~loop[0] && data[cell]) {
next = loop[0];
} else {
loop.shift();
}
}
};
while (next < code.length) {
operation[code[next++]]();
}
if (brainfuck.end) {
brainfuck.end();
}
}
只需调用
brainfuck(source)
即可运行解释器,其中source
是一些Brainfuck源代码。 /> 请注意对
brainfuck.read
,brainfuck.write
和brainfuck.end
的引用。由于输入和输出在很大程度上取决于主机环境,因此由实现来提供这些内容。以下是一些基于浏览器的实现的代码,其中console.log
用于输出,prompt
用于输入(节点过于简单明了。)。Wikipedia和Simon的FizzBuzz程序上的两个示例。您可以在此处进行测试,只需将一些代码粘贴到框中并单击“开始”即可。此实现使用-1作为输入结尾字符,以适应Wikipedia上的ROT13示例(该测试已预装了该示例)。
我正在寻找评论通常的东西。特别需要注意的是:
if (~loop[0])
条件看起来有点重复。也许循环操作符
[
和]
可以更干净地处理。不确定要调用什么
关于
if (~loop[0])
的明显重复的说明:我仍在尝试确定这是否是真正的重复。将其移至主循环似乎在某种程度上打破了关注点的分离……主循环必须比应该更多地了解运算符。如果您决定添加更多运算符(brainfuck ++?),则可能必须更改主循环,而不必担心新的运算符。在我看来,担心自己的行为是每个操作员的工作,不应从其他任何地方进行控制。话虽如此,一个更好的选择可能是事先分析程序以确定每个循环的开始和结束位置。然后,如果不打算执行循环,则只需将
next
设置为循环结束后的操作即可,而不必在途中移至每个操作并使其不运行。这样可以提高性能,还可以检测出损坏的程序并及早引发错误,而不是尝试运行它们并可能陷入无限循环。此处的新版本: JavaScript中的Brainfuck解释器,需2
#1 楼
干净整洁。我喜欢。因为我不了解Brainfuck,所以我什至不会试图弄清它的解释是否正确,所以我会用你的话来看看JS :)您自己指出,
[
和]
运算符的处理方式可能有所不同,这也使您可以避免使用if(~loop[0])
,因为在所有其他运算符中都使用了它。例如:
switch(code[next++]) {
case '[':
loop.unshift(data[cell] ? next : -1);
break;
case ']':
if (~loop[0] && data[cell]) {
next = loop[0];
} else {
loop.shift();
}
break;
default:
if(~loop[0]) operations[op]();
}
本身不是超级干净的,但是会清理操作员的功能。无论如何,那是低下的果实,还有更多的方法,无疑是更清洁的方法来解决它。
但是,我更担心只有一个口译员这一事实。我似乎更干净:
从
brainfuck
返回一个对象(它不必是构造函数;返回对象文字可以正常工作),它使您能够仅将read
,write
和end
函数附加到该实例。当然,这也需要run
函数或类似的东西来开始解释。并且
read
,write
和end
被拔除/具有默认实现,因此,如果我不需要的话,也不必提供它们不在乎例如。也许我只是想看看代码是否可以运行,但是我所关心的全部输出都可以转到/dev/null
(或者在这种情况下为无操作功能)。我想像一下:
var interpreter = brainfuck(source);
interpreter.read = ...
interpreter.write = ...
interpreter.run();
(如果这仅适用于Node,那么将其基于EventEmitter并对IO使用常规事件处理可能会很有趣)
或立即运行代码,但提供读/写功能作为参数,例如:
brainfuck(source, reader, writer [, end])
。要点是避免使用单个
brainfuck
函数可对您传递的任何源代码进行操作,但只有一组“全局” I / O功能。好吧,好吧,您将不会并行运行解释器或其他任何东西,但是对我来说,让每次运行都拥有整洁的状态似乎更加干净。
但实际上,我找不到太多发表评论。真的很干净。
评论
\ $ \ begingroup \ $
好建议。我想到了像您在这里提到的那样“更经典”的设计,但是决定简化。提供一些存根可能是个好主意,即使它们只是抛出错误或不执行任何操作并发出警告。
\ $ \ endgroup \ $
–达格
2014年7月26日在1:44
\ $ \ begingroup \ $
还考虑过将〜loop [0]检查移到主循环中,但是我真的很喜欢循环几乎什么都不做的想法。这样,您就不必真正考虑主循环,而只需专注于操作。我认为最好有某种“快进”功能可以跳到循环的结尾并完全避免〜loop [0]检查,但是...
\ $ \ endgroup \ $
–达格
14年7月26日在1:45
\ $ \ begingroup \ $
我喜欢口译员的想法,除了它向后看。使用interpreter.run(source)而不是为每个源都创建一个新的解释器是否更有意义?
\ $ \ endgroup \ $
–伊兹卡塔
2014年7月26日在3:08
\ $ \ begingroup \ $
@Dagg我同意循环是一个非常干净的循环,但是代价是函数的重复。就个人而言,我宁愿以不那么干净的循环为代价减少重复,但是无论哪种方式都有效。此外,该循环可能比我上面的尝试更干净。
\ $ \ endgroup \ $
– Flaambino
14年7月26日在11:03
\ $ \ begingroup \ $
@Izkata是的,很好。我想我可能更喜欢您的建议,或者上面的第二种形式,您将回调作为参数传递给它,然后立即运行。
\ $ \ endgroup \ $
– Flaambino
14年7月26日在11:05
评论
至少不是相反:用Brainfuck编写的JavaScript解释器@AlexL,因为这将非常困难。