Daniel Pumford's Website

Brainfuck Interpreter (hide)

Brainfuck is an esoteric programming language that uses eight single-character commands to navigate and manipulate a 30kb array. It's been a fascinating puzzle to me for a long time, mostly from an implementation point of view. I have coded several interpreters for the language before this one, but this is my first attempt at making one in Javascript.

Source (show)

js/brainfuck.js
require(['/modules/bfmachine.js'], function(bf) {
    $(document).ready(function() {
        bf.start();
        bf.initialize();
    });
    
    $('#reset').click(function() {
        bf.initialize();
        
        $('#output').val('');
        bf.setOutput('');
        
        $('#pc').html('  PC: ' + bf.PC);
        $('#pointer').html('  Pointer: ' + bf.pointer);
        
        updateTable();
        
        if ($('#run').val() != 'Run') {
            $('#run').click();
        }
    });
    
    $('#input').change(function() {
        bf.setInput($('#input').val());
    });
    
    $(".inputButton").click(function() {
        var text = $("#code").val();
        var selectionStart = $("#code").prop("selectionStart");
        var selectionEnd = $("#code").prop("selectionEnd");
        var front = text.substring(0, selectionStart);
        var end = text.substring(selectionEnd, text.length);
        
        $("#code").focus();
        $("#code").val(front + this.value + end);
        $("#code").prop("selectionStart", selectionStart + 1);
        $("#code").prop("selectionEnd", selectionStart + 1);
        $('#code').blur();
        
        bf.setProgram($("#code").val());
    });
    
    $("#backspaceButton").click(function() {
        var text = $("#code").val();
        var selectionStart = $("#code").prop("selectionStart") - 1;
        var selectionEnd = $("#code").prop("selectionEnd");
        var front = text.substring(0, selectionStart);
        var end = text.substring(selectionEnd, text.length);
        
        $("#code").focus();
        $("#code").val(front + end);
        $("#code").prop("selectionStart", selectionStart);
        $("#code").prop("selectionEnd", selectionStart);
        
        bf.setProgram($("#code").val());
    });
    
    $('#code').bind('input propertychange', function() {
        var codeText = $('#code').val().replace(/\n/g, ' ');
        
        bf.setProgram(codeText);
    });
    
    var updateTable = function() {
        for (var x = 0; x < 10; x++) {
            var index = parseInt($('#h' + x).html());
            
            $('#d' + x).html(' ' + bf.getValue(index) + ' (' + String.fromCharCode(bf.getValue(index)) +')');
            
            if (bf.pointer == index) {
                $('#h' + x).css('color', '#000000');
            }
            else {
                $('#h' + x).css('color', '#666');
            }
        }
    };
    
    var runningCode;
    
    var initRunningCode = function() {
        runningCode = window.setInterval(function() {$('#step').click()}, 1000 * getSpeedValue());
    };
    
    var getSpeedValue = function() {
        var speed = parseInt($('#speed').val());
        
        return 1.0 / 16.0 * Math.pow(2, speed - 1);
        
        //1/16, 1/8, 1/4, 1/2
    };
    
    $('#run').click(function() {
        if ($('#run').val() == 'Run') {
            if ($('#speed').val() == '0') {
                var result = bf.executeProgram();
                
                if (result == 0) {
                    console.log("success");
                }
                else if (result == 2) {
                    alert("Program Timed Out");
                    console.log("TimedOut");
                }
                else if (result == 1) {
                    alert("Program needs more input");
                    console.log('More input');
                }
                
                $('#output').val(bf.getOutput());
                $('#input').val(bf.getInput());
                $('#pc').html('  PC: ' + bf.PC);
                $('#pointer').html('  Pointer: ' + bf.pointer);
                
                updateTable();
            }
            else {
                initRunningCode();
                
                $('#run').val('Stop');
            }
        }
        else {
            clearInterval(runningCode);
            
            $('#run').val('Run');
        }
    });
    
    $('#speed').change(function() {
        var newText;
        
        if ($('#speed').val() == 0) {
            newText = 'Instant';
        }
        else {
            newText = getSpeedValue() + ' sec';
        }
        
        $('#speedText').html('  Speed: ' + newText);
        
        if ($('#run').val() != 'Run') {
            if ($('#speed').val() == 0) {
                $('#run').click();
                $('#run').click();
            }
            
            else {
                clearInterval(runningCode);
                
                initRunningCode();
            }
        }
    });
    
    $('#step').click(function() {
        if (bf.PC >=0 && bf.PC < bf.program.length) {
            var error = bf.executeSymbol(bf.program.charAt(bf.PC));
            
            if (error == 1) {
                alert("Program needs more input");
                
                if ($('#run').val() != 'Run') {
                    clearInterval(runningCode);
            
                    $('#run').val('Run');
                }
            }
            
            $('#output').val(bf.getOutput());
            $('#input').val(bf.getInput());
            $('#pc').html('  PC: ' + bf.PC);
            $('#pointer').html('  Pointer: ' + bf.pointer);
            
            updateTable();
        }
    });
    
    $('#tableLeft').click(function() {
        for (var x = 0; x < 10; x++) {
            var previous = parseInt($('#h' + x).html());
            var next = (previous - 10);
            
            if (next < 0) {
                next += bf.NUM_VALUES;
            }
            
            $('#h' + x).html(next);
        }
        
        updateTable();
    });
    
    $('#tableRight').click(function() {
        for (var x = 0; x < 10; x++) {
            var previous = parseInt($('#h' + x).html());
            var next = (previous + 10);
            
            if (next >= bf.NUM_VALUES) {
                next -= bf.NUM_VALUES;
            }
            
            $('#h' + x).html(next);
        }
        
        updateTable();
    });
});
modules/bfmachine.js
define(function () {
var bf = {};

bf.start = function() {
bf.pointer = 0;

bf.NUM_VALUES = 30000;
bf.values = [];

bf.MAX_VALUE = 256;

bf.bracketStack = [];

bf.program = "";
bf.PC = 0;

bf.input = "";
bf.output = "";
};

bf.initialize = function() {
    for (var i = 0; i < bf.NUM_VALUES; i++) {
        bf.values[i] = 0;
    }
    
    bf.PC = 0;
    bf.pointer = 0;
};
    
bf.getValue = function(index) {
        if (index != undefined) {
            return bf.values[index];
        }
        else {
        return bf.values[bf.pointer];
        }
};

bf.setValue = function(number) {        
        if (number >= bf.MAX_VALUE) {
            number -= bf.MAX_VALUE;
        }
        else if (number < 0) {
            number += bf.MAX_VALUE;
        }
        
    bf.values[bf.pointer] = number;
};

bf.getPointer = function() {
    return bf.pointer;
};

bf.setPointer = function(newPointer) {
        if (newPointer >= bf.NUM_VALUES) {
            newPointer -= bf.NUM_VALUES;
        }
        else if (newPointer < 0) {
            newPointer += bf.NUM_VALUES;
        }

    bf.pointer = newPointer;
};

bf.setProgram = function(prog) {
    bf.program = prog;
};

bf.getProgram = function() {
    return bf.program;
};

bf.getInput = function() {
    return bf.input;
};

bf.setInput = function(inp) {
    bf.input = inp;
};

bf.getOutput = function() {
    return bf.output;
};

bf.setOutput = function(out) {
    bf.output = out;
};

bf.executeSymbol = function(symbol) {
    switch (symbol) {
        case '+':
            bf.setValue(bf.getValue() + 1);
            bf.PC++;
            break;
        case '-':
            bf.setValue(bf.getValue() - 1);
            bf.PC++;
            break;
        case '>':
            bf.setPointer(bf.getPointer() + 1);
            bf.PC++;
            break;
        case '<':
            bf.setPointer(bf.getPointer() - 1);
            bf.PC++;
            break;
        case '[':
            if (bf.getValue() != 0) {
                bf.bracketStack.push(bf.PC);
            }
            else {
                var bracketCounter = 1;
                
                while(bracketCounter != 0) {
                    bf.PC++;
                    
                    if (bf.program.charAt(bf.PC) == '[') {
                        bracketCounter++;
                    }
                    else if (bf.program.charAt(bf.PC) == ']') {
                        bracketCounter--;
                    }
                }
            }
            bf.PC++;
            break;
        case ']':
            bf.PC = bf.bracketStack.pop();
            break;
        case '.':
            bf.output += String.fromCharCode(bf.values[bf.pointer]);
            bf.PC++;
            break;
        case ',':
                if (bf.input.length > 0) {
                bf.setValue(bf.input.charCodeAt(0));
                bf.input = bf.input.substring(1);
                
                    console.log(bf.input);
                
                bf.PC++;
                }
                else {
                    return 1; //Need more input
                }
            break;
        default:
            bf.PC++;
            break;
    }
        
        return 0; //No errors
}

bf.executeProgram = function() {
    var tempCounter = 0;
    var timeout = 10000000;
    
    while(bf.PC < bf.program.length && tempCounter < timeout) {
        var error = bf.executeSymbol(bf.program.charAt(bf.PC));
            
            if (error != 0) {
                return error;
            }

        tempCounter++;
    }
        
        if (tempCounter < timeout) {
            return 0; //Program ran well
        }
        else {
            return 2; //Program ran too long
        }
};

return bf;
});

...

...

Updates (show)

Version 0.1.3
  • Made the app more mobile-friendly.
Version 0.1.2
  • Added ability to step through code.
  • The machine can now be run at variable speeds.
  • The data array is displayed at the bottom of the app.
Version 0.1.1
  • Updated the description on the right side of the screen.
Version 0.1
  • Initial release. It basically works.

...




  Speed: Instant   PC: 0  Pointer: 0

0 1 2 3 4 5 6 7 8 9
0 () 0 () 0 () 0 () 0 () 0 () 0 () 0 () 0 () 0 ()

Brainfuck is a pretty awesome esotaric language that only uses 8 characters.

Symbol Description
+ Add 1 to the current selected cell. Rolls over to 0 from 255.
- Subtract 1 from the current selected cell. Rolls over to 255 from 0.
> Select the next cell to the right of the current selected cell.
< Select the previous cell to the left of the current selected cell.
[ If the current selected cell is not zero, move on to the next command. Otherwise, jump to the closing ']'.
] If the current selected cell is not zero, jump to the opening '['. Otherwise, move on to the next command.
. Output the ASCII value of the current selected cell.
, Input an ASCII value into the current selected cell.