blob: 19a828d419c34c571fdd3df5c7aa77f07018841f [file] [log] [blame] [raw]
// Copyright (c) 2012, Matt Godbolt
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
var pendingTimeout = null;
var asmCodeMirror = null;
var cppEditor = null;
var lastRequest = null;
var currentAssembly = null;
var ignoreChanges = false;
function parseLines(lines, callback) {
var re = /^\<stdin\>:([0-9]+):([0-9]+):\s+(.*)/;
$.each(lines.split('\n'), function(_, line) {
line = line.trim();
if (line != "") {
var match = line.match(re);
if (match) {
callback(parseInt(match[1]), match[3]);
} else {
callback(null, line);
}
}
});
}
function clearBackground(cm) {
for (var i = 0; i < cm.lineCount(); ++i) {
cm.setLineClass(i, null, null);
}
}
var hasErrors = false;
function onCompileResponse(data) {
var stdout = data.stdout || "";
var stderr = data.stderr || "";
if (data.code == 0) {
stdout += "\nCompiled ok";
} else {
stderr += "\nCompilation failed";
}
$('.result .output :visible').remove();
hasErrors = false;
parseLines(stderr + stdout, function(lineNum, msg) {
var elem = $('.result .output .template').clone().appendTo('.result .output').removeClass('template');
if (lineNum) {
if (!hasErrors) {
clearBackground(cppEditor);
}
hasErrors = true;
cppEditor.setLineClass(lineNum - 1, null, "error");
elem.html($('<a href="#">').append(lineNum + " : " + msg)).click(function() {
cppEditor.setSelection({line: lineNum - 1, ch: 0}, {line: lineNum, ch: 0});
return false;
});
} else {
elem.text(msg);
}
});
currentAssembly = data.asm || "[no output]";
updateAsm();
}
function numberUsedLines(asm) {
var sourceLines = {};
$.each(asm, function(_, x) { if (x.source) sourceLines[x.source - 1] = true; });
var ordinal = 0;
$.each(sourceLines, function(k, _) { sourceLines[k] = ordinal++; });
var asmLines = {};
$.each(asm, function(index, x) { if (x.source) asmLines[index] = sourceLines[x.source - 1]; });
return { source: sourceLines, asm: asmLines };
}
var lastUpdatedAsm = null;
function updateAsm(forceUpdate) {
if (!currentAssembly) return;
var newFilters = getAsmFilters();
var hashedUpdate = JSON.stringify({
asm: currentAssembly,
filter: newFilters
});
if (!forceUpdate && lastUpdatedAsm == hashedUpdate) { return; }
lastUpdatedAsm = hashedUpdate;
var asm = processAsm(currentAssembly, newFilters);
var asmText = $.map(asm, function(x){ return x.text; }).join("\n");
var numberedLines = numberUsedLines(asm);
asmCodeMirror.setValue(asmText);
if (!hasErrors) {
clearBackground(cppEditor);
clearBackground(asmCodeMirror);
if (newFilters.colouriseAsm) {
$.each(numberedLines.source, function(line, ordinal) {
cppEditor.setLineClass(parseInt(line), null, "rainbow-" + (ordinal & 7));
});
$.each(numberedLines.asm, function(line, ordinal) {
asmCodeMirror.setLineClass(parseInt(line), null, "rainbow-" + (ordinal & 7));
});
}
}
}
function onChange() {
if (ignoreChanges) return; // Ugly hack during startup.
if (pendingTimeout) clearTimeout(pendingTimeout);
pendingTimeout = setTimeout(function() {
var data = {
source: cppEditor.getValue(),
compiler: $('.compiler').val(),
options: $('.compiler_options').val()
};
window.localStorage['compiler'] = data.compiler;
window.localStorage['compilerOptions'] = data.options;
if (data == lastRequest) return;
lastRequest = data;
$.ajax({
type: 'POST',
url: '/compile',
dataType: 'json',
data: data,
success: onCompileResponse});
}, 750);
window.localStorage['code'] = cppEditor.getValue();
window.localStorage['filter'] = JSON.stringify(getAsmFilters());
updateAsm();
$('a.permalink').attr('href', '#' + serialiseState());
}
function getSource() {
var source = $('.source').val();
if (source == "browser") {
if (window.localStorage['files'] == undefined) window.localStorage['files'] = "{}";
return {
list: function(callback) {
var files = JSON.parse(window.localStorage['files']);
callback($.map(files, function(val, key) { return val; }));
},
load: function(name, callback) {
var files = JSON.parse(window.localStorage['files']);
callback(files[name]);
},
save: function(obj, callback) {
var files = JSON.parse(window.localStorage['files']);
files[obj.name] = obj;
window.localStorage['files'] = JSON.stringify(files);
callback(true);
}
};
} else {
var base = "/source/" + source;
return {
list: function(callback) { $.getJSON(base + "/list", callback); },
load: function(name, callback) { $.getJSON(base + "/load/" + name, callback); },
save: function(obj, callback) { alert("Coming soon..."); }
};
}
}
var currentFileList = {};
function updateFileList() {
getSource().list(function(results) {
currentFileList = {};
$('.filename option').remove();
$.each(results, function(index, arg) {
currentFileList[arg.name] = arg;
$('.filename').append($('<option value="' + arg.urlpart + '">' + arg.name + '</option>'));
if (window.localStorage['filename'] == arg.urlpart) $('.filename').val(arg.urlpart);
});
});
}
function onSourceChange() {
updateFileList();
window.localStorage['source'] = $('.source').val();
}
function loadFile() {
var name = $('.filename').val();
window.localStorage['filename'] = name;
getSource().load(name, function(results) {
if (results.file) {
cppEditor.setValue(results.file);
} else {
// TODO: error?
console.log(results);
}
});
}
function saveFile() {
saveAs($('.files .filename').val());
}
function saveAs(filename) {
var prevFilename = window.localStorage['filename'] || "";
if (filename != prevFilename && currentFileList[filename]) {
// TODO!
alert("Coming soon - overwriting files");
return;
}
var obj = { urlpart: filename, name: filename, file: cppEditor.getValue() };
getSource().save(obj, function(ok) {
if (ok) {
window.localStorage['filename'] = filename;
updateFileList();
}
});
}
function saveFileAs() {
$('#saveDialog').modal();
$('#saveDialog .save-filename').val($('.files .filename').val());
$('#saveDialog .save-filename').focus();
function onSave() {
$('#saveDialog').modal('hide');
saveAs($('#saveDialog .save-filename').val());
};
$('#saveDialog .save').click(onSave);
$('#saveDialog .save-filename').keyup(function(event) {
if (event.keyCode == 13) onSave();
});
}
function serialiseState() {
var state = {
version: 2,
source: cppEditor.getValue(),
compiler: $('.compiler').val(),
options: $('.compiler_options').val(),
filterAsm: getAsmFilters()
};
return encodeURIComponent(JSON.stringify(state));
}
function deserialiseState(state) {
try {
var state = $.parseJSON(decodeURIComponent(state));
if (state.version == 1) {
state.filterAsm = {};
}
else if (state.version != 2) return false;
} catch (ignored) { return false; }
cppEditor.setValue(state.source);
$('.compiler').val(state.compiler);
$('.compiler_options').val(state.options);
setFilterUi(state.filterAsm);
// Somewhat hackily persist compiler into local storage else when the ajax response comes in
// with the list of compilers it can splat over the deserialized version.
// The whole serialize/hash/localStorage code is a mess! TODO(mg): fix
window.localStorage['compiler'] = state.compiler;
updateAsm(true); // Force the update to reset colours after calling cppEditor.setValue
return true;
}
function initialise() {
ignoreChanges = true; // Horrible hack to avoid onChange being called on first starting, ie before we've set anything up.
cppEditor = CodeMirror.fromTextArea($("#c")[0], {
lineNumbers: true,
matchBrackets: true,
useCPP: true,
mode: "text/x-c++src",
onChange: onChange
});
asmCodeMirror = CodeMirror.fromTextArea($(".asm textarea")[0], {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-asm",
readOnly: true
});
if (window.localStorage['code']) cppEditor.setValue(window.localStorage['code']);
if (window.localStorage['compilerOptions']) $('.compiler_options').val(window.localStorage['compilerOptions']);
var defaultFilters = JSON.stringify(getAsmFilters());
setFilterUi($.parseJSON(window.localStorage['filter'] || defaultFilters));
$('form').submit(function() { return false; });
$('.compiler').change(onChange);
$('.compiler_options').change(onChange).keyup(onChange);
$.getJSON("/compilers", function(results) {
$('.compiler option').remove();
$.each(results, function(index, arg) {
$('.compiler').append($('<option value="' + arg.exe + '">' + arg.version + '</option>'));
if (window.localStorage['compiler'] == arg.exe) {
$('.compiler').val(arg.exe);
}
});
onChange();
});
$('.files .source').change(onSourceChange);
$.getJSON("/sources", function(results) {
$('.source option').remove();
$.each(results, function(index, arg) {
$('.files .source').append($('<option value="' + arg.urlpart + '">' + arg.name + '</option>'));
if (window.localStorage['source'] == arg.urlpart) {
$('.files .source').val(arg.urlpart);
}
});
onSourceChange();
});
$('.files .load').click(function() {
loadFile();
return false;
});
$('.files .save').click(function() {
saveFile();
return false;
});
$('.files .saveas').click(function() {
saveFileAs();
return false;
});
$('.filter button.btn').click(function(e) {
$(e.target).toggleClass('active');
onChange();
});
function loadFromHash() {
deserialiseState(window.location.hash.substr(1));
}
$(window).bind('hashchange', function() {
loadFromHash();
});
loadFromHash();
ignoreChanges = false;
}
function getAsmFilters() {
var asmFilters = {};
$('.filter button.btn.active').each(function() {
asmFilters[$(this).val()] = true;
});
return asmFilters;
}
function setFilterUi(asmFilters) {
$('.filter button.btn').each(function() {
$(this).toggleClass('active', !!asmFilters[$(this).val()]);
});
}
$(initialise);