The previous version is here. This version takes suggestions from that review into account:
brainfuck
is now an object instead of a function, andbrainfuck.run(source)
kicks off the interpreter.- There is no built-in support for multiple interpreter instances, but
Object.create(brainfuck)
will do the trick. - Stub functions (which throw errors) are provided for the implementation-dependent
read
andwrite
functions.
This version also addresses the redundancy in the first version by handling loops differently. All loop start and end positions are pre-calculated, so the entire loop is now bypassed when appropriate instead of passing over each operation and no-opping. This also removes the need to keep a stack of loop start positions while the program runs.
Also, this version adds proper exports for AMD script loaders and CJS environments, with a fallback to the global namespace object.
(function(global){
// Find start and end positions of each loop.
function findLoops(code) {
var start;
var startpoints = {};
var endpoints = {};
var stack = [];
for (var i = 0; i < code.length; i++) {
if (code[i] == '[') {
stack.push(i);
} else if (code[i] == ']') {
start = stack.pop();
startpoints[i] = start;
endpoints[start] = i;
}
}
return { start: startpoints, end: endpoints };
}
// Run the brainfuck interpreter.
function run(source) {
var brainfuck = this;
var code = source.replace(/[^-+<>.,[\]]/g, '').split(''); // program code
var loop = findLoops(code); // loop start and end positions
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 () { ++cell; },
'<': function () { --cell; },
'+': function () { data[cell] = (data[cell] || 0) + 1; },
'-': function () { data[cell] = (data[cell] || 0) - 1; },
'.': function () { brainfuck.write(data[cell]); },
',': function () { data[cell] = brainfuck.read(); },
'[': function () { if (!data[cell]) { next = loop.end[next]; } },
']': function () { if (data[cell]) { next = loop.start[next]; } }
};
while (next < code.length) {
operation[code[next]]();
next++;
}
if (brainfuck.end) {
brainfuck.end();
}
}
// Export a module for AMD loaders, CJS environments, or as a global.
function exportModule(name, module) {
if (global['define'] && global['define']['amd']) {
global['define'](module);
} else if (global['exports']) {
for (var key in module) if (module.hasOwnProperty(key)) {
global['exports'][key] = module[key];
}
} else {
global[name] = module;
}
}
// Export brainfuck module.
exportModule('brainfuck', {
'run': run,
'read': function() { throw new Error('"read" function not provided'); },
'write': function() { throw new Error('"write" function not provided'); }
});
}(this));
Test it out here. I have no specific concerns at this point, I'm just looking for a general review.
exportModulo
for the exports case. Also most people domodule.exports = smt
\$\endgroup\$module.exports =
only works in node (afaik), it's not a standard CJS modules thing. Where is the typo, I'm not seeing it? \$\endgroup\$(var key in value)
you wantmodule
\$\endgroup\$value
everywhere but I changed it at the last minute. Fixed. :) \$\endgroup\$]
will result instack.pop() => undefined
infindLoops
which could lead to some breakage. I think. Point is, it'd be easy to check for unmatched brackets and complain upfront - but I'm not saying it's worth it. I mean, it is Brainfuck after all \$\endgroup\$