window.addEventListener('load', function () {
document.getElementById('run-button').addEventListener('click', function (event) {
Brainfuck.run();
});
document.getElementById('stop-button').addEventListener('click', function (event) {
Brainfuck.stop();
});
document.getElementById('step-button').addEventListener('click', function (event) {
Brainfuck.step();
});
document.getElementById('rewind-button').addEventListener('click', function (event) {
Brainfuck.init();
});
document.getElementById('show-code').addEventListener('change', function (event) {
Brainfuck.init();
});
document.getElementById('show-memory').addEventListener('change', function (event) {
Brainfuck.init();
});
document.getElementById('slow').addEventListener('change', function (event) {
Brainfuck.setSpeed();
});
document.getElementById('memory-size').addEventListener('change', function (event) {
Brainfuck.init();
});
document.getElementById('code').addEventListener('change', function (event) {
Brainfuck.init();
});
document.getElementById('code').addEventListener('keydown', function (event) {
if (event.keyCode == 65 && event.ctrlKey) {
event.target.select();
}
});
document.getElementById('code').value =
'>++++++++[<+++++++++>-]<.---.+++++++..+++.>++++++[<-------->-]<+.>+++++[<+++++++++++>-]<.--------.+++.------.--------.>+++++[<------->-]<.';
});
var Brainfuck = (function () {
const MEMORY_MAX = 30000;
const CODE_MAX = 30000;
const SLOW_INTERVAL = 40;
var _running = false,
_code,
_code_p,
_mem,
_mem_p,
_code_max,
_step,
_input,
_output,
_ended = false,
_initialized = false,
_showCode,
_showMemory,
_memorySize,
_slow,
_codePElem = null,
_memPElem = null;
function init() {
setErrorMessage('');
var code = document.getElementById('code').value;
_showCode = document.getElementById('show-code').checked;
_showMemory = document.getElementById('show-memory').checked;
setSpeed();
var memorySize = document.getElementById('memory-size').value;
if (memorySize == '' || !memorySize.match(/^[0-9]+$/)) {
setErrorMessage('メモリのサイズは正の整数で入力してください。');
return;
} else if (memorySize > MEMORY_MAX) {
setErrorMessage('メモリのサイズは' + MEMORY_MAX + '以下で入力してください。');
return;
}
_memorySize = memorySize;
if (code.length > CODE_MAX) {
setErrorMessage('コードは' + CODE_MAX + '文字以下で入力してください。');
return;
}
setupCodeView(code);
_output = '';
_input = document.getElementById('input').value;
_code = code; //code.replace(/\s/g, '');
_code_p = 0;
//_mem = new Uint8Array(_memorySize);
_mem = [];
for (var i = 0; i < _memorySize; ++i) {
_mem[i] = 0;
}
_mem_p = 0;
_code_max = Math.min(_code.length - 1, CODE_MAX);
_step = 0;
setupMemoryView(_mem);
_running = false;
_ended = false;
_initialized = true;
updateView();
}
function setSpeed() {
_slow = document.getElementById('slow').checked;
}
function setErrorMessage(msg) {
document.getElementById('error').innerHTML = msg;
}
function setupCodeView(code) {
var view = '';
var wrapper = document.getElementById('code-view-wrapper');
if (!_showCode) {
wrapper.style.display = 'none';
} else {
for (var i = 0, len = code.length; i < len; ++i) {
view += '' + code.charAt(i) + '';
}
wrapper.style.display = 'block';
}
document.getElementById('code-view').innerHTML = view;
}
function setupMemoryView(mem) {
var view = '';
var wrapper = document.getElementById('memory-view-wrapper');
if (!_showMemory) {
wrapper.style.display = 'none';
} else {
for (var i = 0, len = mem.length; i < len; ++i) {
if (view != '') {
view += ' ';
}
view += '' + (i <= 0 ? '0' : '') + '';
}
wrapper.style.display = 'block';
}
document.getElementById('memory-view').innerHTML = view;
}
function run() {
try {
if (!_initialized || _ended) {
init();
}
document.getElementById('run-button').style.display = 'none';
document.getElementById('stop-button').style.display = 'inline';
document.getElementById('step-button').disabled = true;
_running = true;
var ret;
var loop = function () {
try {
if (_running) {
process();
}
if (_slow) {
setTimeout(loop, SLOW_INTERVAL);
} else {
setZeroTimeout(loop);
}
} catch (e) {
errorHandler(e);
}
};
loop();
} catch (e) {
errorHandler(e);
}
}
function stop() {
document.getElementById('run-button').style.display = 'inline';
document.getElementById('stop-button').style.display = 'none';
document.getElementById('step-button').disabled = false;
_running = false;
}
function step() {
try {
if (!_initialized || _ended) {
init();
}
process();
} catch (e) {
errorHandler(e);
}
}
function process() {
updateCodeView();
_input = document.getElementById('input').value;
var c;
var nest = 0;
var org_code_p;
switch ((c = _code.charAt(_code_p))) {
case '>':
++_mem_p;
if (_mem_p > MEMORY_MAX) {
throw new Error('error: ポインタがメモリ上限をはみ出しました... ' + (_code_p + 1) + '文字目');
}
break;
case '<':
--_mem_p;
if (_mem_p < 0) {
throw new Error('error: ポインタがメモリ下限をはみ出しました... ' + (_code_p + 1) + '文字目');
}
break;
case '+':
if (++_mem[_mem_p] > 255) {
_mem[_mem_p] = 0;
}
break;
case '-':
if (--_mem[_mem_p] < 0) {
_mem[_mem_p] = 255;
}
break;
case '.':
putchar(_mem[_mem_p]);
break;
case ',':
_mem[_mem_p] = getchar();
break;
case '[':
if (_mem[_mem_p] === 0) {
nest = 0;
org_code_p = _code_p;
while (true) {
if (++_code_p > _code_max) {
throw new Error('error: 対応する ] が見つかりませんでした... ' + (org_code_p + 1) + '文字目');
}
if (_code.charAt(_code_p) === ']' && nest-- <= 0) {
break;
} else if (_code.charAt(_code_p) === '[') {
++nest;
}
}
}
break;
case ']':
if (_mem[_mem_p] !== 0) {
nest = 0;
org_code_p = _code_p;
while (true) {
if (--_code_p < 0) {
throw new Error('error: 対応する [ が見つかりませんでした... ' + (org_code_p + 1) + '文字目');
}
if (_code.charAt(_code_p) === '[' && nest-- <= 0) {
break;
} else if (_code.charAt(_code_p) === ']') {
++nest;
}
}
}
break;
}
++_step;
updateView();
updateMemoryView();
++_code_p;
if (_code_p > _code_max) {
_ended = true;
stop();
return;
}
}
function getchar() {
var newVal = '';
var ret = null;
for (var i = 0, len = _input.length; i < len; ++i) {
if (i === 0) {
ret = _input.charCodeAt(i);
} else {
newVal += _input.charAt(i);
}
}
_input = newVal;
return ret;
}
function putchar(c) {
_output += String.fromCharCode(c);
}
function updateView() {
document.getElementById('input').value = _input;
document.getElementById('output').innerHTML = _output;
document.getElementById('step').innerHTML = _step;
}
function updateCodeView() {
if (!_showCode) {
return;
}
if (_codePElem !== null) {
_codePElem.classList.remove('code_p');
}
_codePElem = document.getElementById('code_' + _code_p);
if (_codePElem) {
_codePElem.classList.add('code_p');
}
}
function updateMemoryView() {
if (!_showMemory) {
return;
}
if (_memPElem !== null) {
_memPElem.classList.remove('mem_p');
}
_memPElem = document.getElementById('mem_' + _mem_p);
if (_memPElem) {
_memPElem.classList.add('mem_p');
_memPElem.innerHTML = _mem[_mem_p];
}
}
function errorHandler(e) {
setErrorMessage(e.message);
stop();
throw e; // TODO
}
/**
* thanks
* https://dbaron.org/log/20100309-faster-timeouts
*/
var setZeroTimeout = (function () {
var timeouts = [];
var messageName = 'zero-timeout-message';
window.addEventListener(
'message',
function (event) {
if (event.source === window && event.data === messageName) {
if (event.stopPropagation) {
event.stopPropagation();
}
if (timeouts.length) {
var fn = timeouts.shift();
fn();
}
}
},
true
);
return function (fn) {
timeouts.push(fn);
window.postMessage(messageName, '*');
};
})();
return {
init: init,
run: run,
stop: stop,
step: step,
setSpeed: setSpeed,
};
})();