Last active
October 2, 2015 11:36
-
-
Save EnoahNetzach/bbc4bdde86358eb37d5e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 = ' '; | |
} 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) ? ' ' : '' ; | |
$(`#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