Skip to content

Instantly share code, notes, and snippets.

@EnoahNetzach
Last active October 2, 2015 11:36
Show Gist options
  • Save EnoahNetzach/bbc4bdde86358eb37d5e to your computer and use it in GitHub Desktop.
Save EnoahNetzach/bbc4bdde86358eb37d5e to your computer and use it in GitHub Desktop.
<style type="text/css">
code {
color: inherit;
background-color: inherit;
}
.empty_literal_bit {
color: #333;
}
.active_bit,
.active_literal_bit,
.active_cursor {
font-weight: 700;
}
</style>
<div class="container-fluid">
<h1>Brainfuck interactive interpreter</h1>
<form>
<h3>Program</h3>
<div class="form-group">
<textarea class="form-control" id="program_elem" placeholder="Insert your program here"></textarea>
</div>
<pre id="program_interface_elem" class="form-control-static hidden">
</pre>
<h3>Options</h3>
<div class="form-group">
<div class="row">
<div class="col-sm-4">
<div class="input-group">
<div class="input-group-addon">Memory (x 12)</div>
<input type="number" class="form-control" id="dimension_elem" value="30">
</div>
</div>
<div class="col-sm-3">
<div class="input-group">
<div class="input-group-addon">Speed</div>
<input type="number" class="form-control" id="speed_elem" value="100">
</div>
</div>
<div class="col-sm-5">
<div class="input-group">
<div class="input-group-addon">Bit depth</div>
<input type="number" class="form-control" id="bit_depth_elem" value="256">
<span class="input-group-addon">
<input type="checkbox" id="bit_unsigned_elem" aria-label="unsigned" checked="checked"> unsigned
</span>
</div>
</div>
</div>
</div>
<h3>Input</h3>
<div class="form-group">
<input type="text" class="form-control" id="input_elem" placeholder="Insert the program input here">
</div>
<h3>Controls</h3>
<div class="form-group">
<button type="button" id="run_btn" class="btn btn-default" disabled="disabled">Run</button>
<button type="button" id="next_btn" class="btn btn-default" disabled="disabled">Next</button>
<button type="button" id="stop_btn" class="btn btn-default" disabled="disabled">Stop</button>
</div>
<h3>Output</h3>
<pre id="output_elem" class="form-control-static">
</pre>
<h3>Memory</h3>
<div class="well form-control-static text-center">
<div>
<div class="row">
<div class="col-sm-1"><b>#</b></div>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-1"><b>%0</b></div>
<div class="col-sm-1"><b>%1</b></div>
<div class="col-sm-1"><b>%2</b></div>
<div class="col-sm-1"><b>%3</b></div>
<div class="col-sm-1"><b>%4</b></div>
<div class="col-sm-1"><b>%5</b></div>
<div class="col-sm-1"><b>%6</b></div>
<div class="col-sm-1"><b>%7</b></div>
<div class="col-sm-1"><b>%8</b></div>
<div class="col-sm-1"><b>%9</b></div>
<div class="col-sm-1"><b>%10</b></div>
<div class="col-sm-1"><b>%11</b></div>
</div>
</div>
<div class="col-sm-3">
<b>print</b>
</div>
</div>
<div id="memory_elem"></div>
</div>
</div>
</form>
</div>
<script type="application/javascript">
"use strict";
$(() => {
function pad(n, width, z) {
z = z || '0';
let sign = Math.sign(n);
n = (n * sign) + '';
return (sign >= 0 ? '' : '-') + (n.length >= width ? n : new Array(width - n.length + 1).join(z) + n);
}
function sanitize_literal(code) {
let literal = '';
if (code > 32 && code <= 126) {
literal = String.fromCharCode(code);
} else if (code == 32) {
literal = '&nbsp';
} else {
literal = '<span class="empty_literal_bit">.</span>';
}
return literal;
}
// get the necessary html elements
const program_elem = $('#program_elem');
const program_interface_elem = $('#program_interface_elem');
const input_elem = $('#input_elem');
const output_elem = $('#output_elem');
const dimension_elem = $('#dimension_elem');
const speed_elem = $('#speed_elem');
const bit_depth_elem = $('#bit_depth_elem');
const bit_unsigned_elem = $('#bit_unsigned_elem');
const memory_elem = $('#memory_elem');
const run_btn = $('#run_btn');
const next_btn = $('#next_btn');
const stop_btn = $('#stop_btn');
// utilities
const memory = [];
let memory_dimension_12 = dimension_elem.val();
let speed = parseInt(speed_elem.val());
let bit_depth = parseInt(bit_depth_elem.val());
let bit_unsigned = bit_unsigned_elem.prop('checked');
let bit_min = bit_unsigned ? 0 : -Math.ceil(bit_depth / 2);
let bit_max = bit_unsigned ? bit_depth - 1 : -bit_min - 1;
let cursor = 0;
let is_running = false;
let event_loop = null;
let program_buf = '';
let program_buf_cursor = 0;
let input_buf = '';
let input_buf_cursor = 0;
let output_buf = '';
function init() {
memory.splice(0, memory.length);
memory_dimension_12 = Math.ceil(dimension_elem.val());
// create the memory
for (let i = 0; i < memory_dimension_12 * 12; i++) {
memory.push(0);
}
// print the interface
memory_elem.empty();
for (let i = 0; i < memory_dimension_12; i++) {
memory_elem.append(`
<div class="row">
<div class="col-sm-1"><i>${pad(i * 12, 5)}</i>:</div>
<div class="col-sm-8">
<div id="byte_${i}" class="row">
</div>
</div>
<div class="col-sm-3">
<code id="literal_byte_${i}">
</code>
</div>
</div>
`);
for (let j = 0; j < 12; j++) {
let bit_n = i * 12 + j;
let bit = memory[bit_n];
$(`#byte_${i}`).append(`
<div id="bit_${bit_n}" class="col-sm-1 bit">
${pad(bit, 3)}
</div>
`);
let spacing = (j != 11) ? '&nbsp' : '' ;
$(`#literal_byte_${i}`).append(`<span id="literal_bit_${bit_n}" class="literal_bit">${sanitize_literal(bit)}</span>${spacing}`);
}
}
}
init();
// interpreter functions
function check_syntax() {
let opened = program_buf.replace(/[^\[]/g, '').length;
let closed = program_buf.replace(/[^\]]/g, '').length;
if (opened !== closed) {
alert('error');
return false;
}
return true;
}
function stop() {
is_running = false;
if (event_loop !== null) {
clearInterval(event_loop);
}
// reset the utilities
cursor = 0;
program_buf = '';
program_buf_cursor = 0;
input_buf = '';
input_buf_cursor = 0;
// reset memory
for (let i = 0; i < memory.length; i++) {
memory[i] = 0;
}
// reset the program input interface
program_interface_elem.html('');
program_elem.parent().removeClass('hidden');
program_interface_elem.addClass('hidden');
// reset buttons
run_btn.removeClass('btn-danger');
next_btn.attr('disabled', 'disabled');
stop_btn.attr('disabled', 'disabled');
}
function pace(show) {
if (!is_running) {
stop();
return;
}
if (show === undefined) {
show = true;
}
// read next command in the buffer
let comm = program_buf[program_buf_cursor];
program_buf_cursor++;
// update the program input interface
if (show) {
$('.active_cursor').removeClass('active_cursor');
$(`#program_buf_${program_buf_cursor - 1}`).addClass('active_cursor');
}
// update memory
switch (comm) {
case '>':
cursor++;
if (cursor >= memory.length) {
cursor = 0;
}
break;
case '<':
cursor--;
if (cursor < 0) {
cursor = memory.length - 1;
}
break;
case '+':
memory[cursor]++;
if (memory[cursor] > bit_max) {
memory[cursor] = bit_min;
}
break;
case '-':
memory[cursor]--;
if (memory[cursor] < bit_min) {
memory[cursor] = bit_max;
}
break;
case ']':
if (memory[cursor] !== 0) {
// go to last opened [
let found = 1;
for (let c = program_buf_cursor - 2; c >= 0; c--) {
if (program_buf[c] == ']') {
found++;
} else if (program_buf[c] == '[') {
found--;
}
if (found === 0) {
program_buf_cursor = c + 1;
break;
}
}
}
break;
case ',':
if (input_buf_cursor < input_buf.length) {
memory[cursor] = input_buf.charCodeAt(input_buf_cursor);
input_buf_cursor++;
}
break;
case '.':
let output = String.fromCharCode(memory[cursor]);
if (show) {
output_elem.append(output);
} else {
output_buf += output;
}
break;
}
// update the interface
if (show) {
$('.active_bit').removeClass('active_bit');
$(`#bit_${cursor}`)
.addClass('active_bit')
.text(pad(memory[cursor], 3))
;
$('.active_literal_bit').removeClass('active_literal_bit');
$(`#literal_bit_${cursor}`)
.addClass('active_literal_bit')
.html(sanitize_literal(memory[cursor]))
;
}
if (program_buf_cursor >= program_buf.length) {
stop();
return;
}
}
function run() {
if (is_running) {
return;
}
program_buf = program_elem.val().replace(/[^\>\<\+\-\[\]\,\.]/g, '');
if (!check_syntax()) {
return;
}
input_buf = input_elem.val();
speed = parseInt(speed_elem.val());
bit_depth = parseInt(bit_depth_elem.val());
if (bit_depth <= 0) {
bit_depth = Infinity;
}
bit_unsigned = bit_unsigned_elem.prop('checked');
bit_min = bit_unsigned ? 0 : -Math.ceil(bit_depth / 2);
bit_max = bit_unsigned ? bit_depth - 1 : -bit_min - 1;
// reset the interface
if (Math.ceil(dimension_elem.val()) !== memory_dimension_12) {
init();
}
$('.active_bit').removeClass('active_bit');
$('.bit').text(pad(0, 3));
$('.active_literal_bit').removeClass('active_literal_bit');
$('.literal_bit').html(sanitize_literal(0));
// convert the program input to an interface
program_interface_elem.html('');
$.each(program_buf.split(''), (i, c) => {
program_interface_elem.append(`<span id="program_buf_${i}">${c}</span>`);
});
program_elem.parent().addClass('hidden');
program_interface_elem.removeClass('hidden');
output_elem.text('');
output_buf = '';
run_btn.addClass('btn-danger');
stop_btn.removeAttr('disabled');
// run
is_running = true;
if (speed > 0) {
event_loop = setInterval(pace, speed);
} else if (speed === 0) {
while (is_running) {
pace(false);
}
output_elem.text(output_buf);
} else {
next_btn.removeAttr('disabled');
}
}
// bind the event functions
program_elem.on('keyup', (event) => {
if (program_elem.val().length > 0) {
run_btn.removeAttr('disabled');
} else {
run_btn.attr('disabled', 'disabled');
}
});
run_btn.on('click', (event) => {
run();
});
next_btn.on('click', (event) => {
pace();
});
stop_btn.on('click', (event) => {
stop();
});
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment