jsbasic/index.js
2013-05-07 20:35:49 -07:00

269 lines
7.2 KiB
JavaScript

window.onload = function () {
var $ = function (s) { return document.querySelector(s); };
$('#lb_files').selectedIndex = 0;
var bell = (function () {
var b = new Bell(/^.*\/|/.exec(window.location)[0]);
return function () { b.play(); };
} ());
var tty = new TTY($('#screen'), $('#keyboard'), bell);
var dos = new DOS(tty);
var lores = new LoRes($('#lores'), 40, 48);
var hires = new HiRes($('#hires'), 280, 192);
var hires2 = new HiRes($('#hires2'), 280, 192);
var display = {
state: { graphics: false, full: true, page1: true, lores: true },
setState: function (state, value /* ... */) {
var args = Array.prototype.slice.call(arguments);
while (args.length) {
state = args.shift();
value = args.shift();
this.state[state] = value;
}
if (this.state.graphics) {
lores.show(this.state.lores);
hires.show(!this.state.lores && this.state.page1);
hires2.show(!this.state.lores && !this.state.page1);
tty.splitScreen(tty.getScreenSize().height - (this.state.full ? 0 : 4));
} else {
lores.show(false);
hires.show(false);
hires2.show(false);
tty.splitScreen(0);
}
}
};
var pdl = [0, 0, 0, 0];
// Lexical highlighting, if available
var editor;
if (typeof CodeMirror === 'function') {
editor = new CodeMirror($('#editorframe'), {
mode: 'basic',
tabMode: 'default',
content: $('#source').value,
height: '100%'
});
} else {
editor = (function () {
var textArea = document.createElement('textarea');
$('#editorframe').appendChild(textArea);
textArea.style.width = '598px';
textArea.style.height = '384px';
return {
getValue: function () {
return textArea.value;
},
setValue: function (value) {
textArea.value = value;
},
setCursor: function (line, column) {
// TODO: Implement me!
}
};
}());
}
$('#btn_share').onclick = function () {
// Load up the hidden text area with the current source
$('#source').value = getSource();
};
function getSource() {
return editor.getValue();
}
function setSource(source) {
editor.setValue(source);
}
// Do not let certain events take focus away from "keyboard"
function keyboardFocus(e) {
tty.focus();
e.stopPropagation(); // W3C
e.preventDefault(); // e.g. to block arrows from scrolling the page
}
addEvent($('#lores'), 'click', keyboardFocus);
addEvent($('#hires'), 'click', keyboardFocus);
addEvent($('#hires2'), 'click', keyboardFocus);
addEvent($('#screen'), 'click', keyboardFocus);
addEvent($('#frame'), 'click', keyboardFocus);
addEvent($('#frame'), 'blur', keyboardFocus); // Needed on IE, not sure why it tries to get focus
addEvent($('#keyboard'), 'focus', function () {
getClassList($('#frame')).add('focused');
});
addEvent($('#keyboard'), 'blur', function () {
getClassList($('#frame')).remove('focused');
});
var program;
addEvent($('#btn_run'), 'click', function () {
dos.reset();
tty.reset();
tty.autoScroll = true;
try {
program = basic.compile(getSource());
} catch (e) {
if (e instanceof basic.ParseError) {
editor.setCursor({ line: e.line - 1, ch: e.column - 1 });
console.log(e.message +
' (source line:' + e.line + ', column:' + e.column + ')');
}
alert(e);
return;
}
stopped = false;
updateUI();
program.init({
tty: tty,
hires: hires,
hires2: hires2,
lores: lores,
display: display,
paddle: function (n) { return pdl[n]; }
});
setTimeout(driver, 0);
});
addEvent($('#btn_stop'), 'click', function () {
tty.reset(); // cancel any blocking input
stopped = true;
updateUI();
});
addEvent($('#lb_files'), 'change', function () {
var sel = $('#lb_files');
loadFile('samples/' + sel.value + ".txt", setSource);
});
addEvent($('#btn_save'), 'click', function () {
window.localStorage.setItem("save_program", getSource());
$('#btn_load').disabled = false;
});
addEvent($('#btn_load'), 'click', function () {
setSource(window.localStorage.getItem("save_program"));
});
$('#btn_load').disabled = (window.localStorage.getItem("save_program") === null);
// Add a "printer" on demand
addEvent($('#btn_capture'), 'click', function () {
$('#btn_capture').disabled = true;
var printer = new Printer(tty);
printer.onclose = function () {
$('#btn_capture').disabled = false;
};
});
// Mouse-as-Joystick
var wrapper = $('#screen-wrapper');
addEvent(wrapper, 'mousemove', function (e) {
// compute relative coordinates
var x = e.clientX, y = e.clientY, elem = wrapper;
while (elem) {
x -= elem.offsetLeft - elem.scrollLeft;
y -= elem.offsetTop - elem.scrollTop;
elem = elem.offsetParent;
}
function clamp(n, min, max) { return Math.min(Math.max(n, min), max); }
pdl[0] = clamp(x / (wrapper.offsetWidth - 1), 0, 1);
pdl[1] = clamp(y / (wrapper.offsetHeight - 1), 0, 1);
});
var stopped = true;
function updateUI() {
$("#btn_stop").disabled = stopped ? "disabled" : "";
$("#btn_run").disabled = stopped ? "" : "disabled";
$("#lb_files").disabled = stopped ? "" : "disabled";
if (stopped) {
$("#btn_run").focus();
} else {
tty.focus();
}
}
// TODO: Expose a RESPONSIVE |---+--------| FAST slider in the UI
// Number of steps to execute before yielding execution
// (Use a prime to minimize risk of phasing with loops)
var NUM_SYNCHRONOUS_STEPS = 37;
function driver() {
var state = basic.STATE_RUNNING;
var statements = NUM_SYNCHRONOUS_STEPS;
while (!stopped && state === basic.STATE_RUNNING && statements > 0) {
try {
state = program.step(driver);
} catch (e) {
console.log(e);
alert(e.message ? e.message : e);
stopped = true;
updateUI();
return;
}
statements -= 1;
}
if (state === basic.STATE_STOPPED || stopped) {
stopped = true;
updateUI();
} else if (state === basic.STATE_BLOCKED) {
// Fall out
} else { // state === basic.STATE_RUNNING
setTimeout(driver, 0); // Keep going
}
}
function parseQueryParams() {
var params = {};
var query = document.location.search.substring(1);
query.split(/&/g).forEach(function(pair) {
pair = pair.replace(/\+/g, " ").split(/=/).map(decodeURIComponent);
params[pair[0]] = pair.length === 1 ? pair[0] : pair[1];
});
return params;
}
function loadFile(filename, callback) {
var req = new XMLHttpRequest();
var url = encodeURI(filename); // not encodeURIComponent, so paths can be specified
var async = true;
req.open("GET", url, async);
req.onreadystatechange = function () {
if (req.readyState === XMLHttpRequest.DONE) {
if (req.status === 200 || req.status === 0) {
callback(req.responseText);
}
}
};
req.send(null);
}
// load default
var params = parseQueryParams();
if ('source' in params) {
setSource(params.source);
} else {
loadFile('samples/sample.default.txt', setSource);
}
};