HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Brainfuck interpreter in JavaScript

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
brainfuckjavascriptinterpreter

Problem

Just what it says on the tin: a brainfuck interpreter in JavaScript.

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();
    }
}


Just call brainfuck(source), where source is some brainfuck source code, to run the interpreter.

Notice the references to brainfuck.read, brainfuck.write and brainfuck.end. Since input and output are heavily dependent on the host environment, it's up to the implementation to provide these. Here's some code for a browser-based implementation, using `conso

Solution

It's nice and clean; I like it. I won't even try to figure out if its interpretation is correct, since I don't know Brainfuck, so I'll take your word for that and just look at the JS :)

As you yourself pointed out, the [ and ] operators might be handled differently, which would also allow you avoid the if(~loop[0]), since that's used in all the other operators.

For instance:

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]();
}


Not super clean by itself, but it'll clean up the operator functions. Anyway, that's low-hanging fruit, and there are more - and no doubt cleaner - ways to solve it.

However, I'd be more concerned about the fact that there's only the single interpreter. I'd seem cleaner to me to:

-
Return an object from brainfuck (it doesn't have to be a construtor function; returning an object literal would work fine) that allows you to attach the read, write and end functions to just that instance. This would of course also necessitate a run function or something, to kick off the interpretation.

-
And that read, write and end are stubbed out/have default implementations, so I don't have to supply 'em, if I don't care. E.g. maybe I just want to see if the code runs but the output can go to /dev/null for all I care (or, in this case, a no-op function).

I imagine something like:

var interpreter = brainfuck(source);
interpreter.read = ...
interpreter.write = ...
interpreter.run();


(if this was for Node-only, it might be fun to base it off of EventEmitter, and use the usual event handling for IO)

Or run the code immediately, but provide the read/write functions as arguments, e.g.: brainfuck(source, reader, writer [, end]).

Point is just to avoid having one single brainfuck function that will operate on any source code you pass, but has a single, "global" set of I/O functions.

Ok, ok, you're not going to be running interpreters in parallel or anything, but it just seems cleaner to me to let each run have clean slate.

But really, I can't find much to comment on. It's pretty clean as it is.

Code Snippets

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]();
}
var interpreter = brainfuck(source);
interpreter.read = ...
interpreter.write = ...
interpreter.run();

Context

StackExchange Code Review Q#58086, answer score: 6

Revisions (0)

No revisions yet.