Extracted compiler

pull/1/head
Dave Hoover 9 years ago
parent fbe522ebc0
commit b57d751da2
  1. 271
      src/app.js
  2. 203
      src/app/compiler.js

@ -4,8 +4,10 @@ var web3 = require('./web3-adapter.js');
var queryParams = require('./app/query-params');
var gistHandler = require('./app/gist-handler');
var StorageHandler = require('./app/storage-handler');
var Editor = require('./app/editor');
var Compiler = require('./app/compiler');
// The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event.
@ -118,7 +120,7 @@ var run = function() {
$web3Toggle.on('change', executionContextChange );
$web3endpoint.on('change', function() {
setProviderFromEndpoint();
if (executionContext == 'web3') compile();
if (executionContext == 'web3') compiler.compile();
});
function executionContextChange (ev) {
@ -129,7 +131,7 @@ var run = function() {
executionContext = ev.target.value;
setProviderFromEndpoint();
}
compile();
compiler.compile();
}
function setProviderFromEndpoint() {
@ -433,7 +435,7 @@ var run = function() {
setEditorSize( hidingRHP ? 0 : window.localStorage[EDITOR_SIZE_CACHE_KEY] );
$('.toggleRHP i').toggleClass('fa-angle-double-right', !hidingRHP);
$('.toggleRHP i').toggleClass('fa-angle-double-left', hidingRHP);
if (!hidingRHP) compile();
if (!hidingRHP) compiler.compile();
});
@ -450,220 +452,6 @@ var run = function() {
document.querySelector('#editorWrap').addEventListener('change', onResize);
// ----------------- compiler ----------------------
var compileJSON;
var compilerAcceptsMultipleFiles;
var previousInput = '';
var sourceAnnotations = [];
var compile = function(missingInputs) {
editor.clearAnnotations();
sourceAnnotations = [];
$('#output').empty();
var input = editor.getValue();
window.localStorage.setItem(editor.getCacheFile(), input);
var files = {};
files[fileNameFromKey(editor.getCacheFile())] = input;
gatherImports(files, missingInputs, function(input, error) {
$('#output').empty();
if (input === null) {
renderError(error);
} else {
var optimize = queryParams.get().optimize;
compileJSON(input, optimize ? 1 : 0);
}
});
};
var compilationFinished = function(result, missingInputs) {
var data;
var noFatalErrors = true; // ie warnings are ok
try {
data = $.parseJSON(result);
} catch (exception) {
renderError('Invalid JSON output from the compiler: ' + exception);
return;
}
if (data['error'] !== undefined) {
renderError(data['error']);
if (errortype(data['error']) !== 'warning') noFatalErrors = false;
}
if (data['errors'] != undefined) {
$.each(data['errors'], function(i, err) {
renderError(err);
if (errortype(err) !== 'warning') noFatalErrors = false;
});
}
if (missingInputs !== undefined && missingInputs.length > 0)
compile(missingInputs);
else if (noFatalErrors && !hidingRHP)
renderContracts(data, editor.getValue());
};
var compileTimeout = null;
var onChange = function() {
var input = editor.getValue();
if (input === "") {
window.localStorage.setItem(editor.getCacheFile(), '');
return;
}
if (input === previousInput)
return;
previousInput = input;
if (compileTimeout) window.clearTimeout(compileTimeout);
compileTimeout = window.setTimeout(compile, 300);
};
var onCompilerLoaded = function() {
if (worker === null) {
var compile;
var missingInputs = [];
if ('_compileJSONCallback' in Module) {
compilerAcceptsMultipleFiles = true;
var missingInputsCallback = Module.Runtime.addFunction(function(path, contents, error) {
missingInputs.push(Module.Pointer_stringify(path));
});
var compileInternal = Module.cwrap("compileJSONCallback", "string", ["string", "number", "number"]);
compile = function(input, optimize) {
missingInputs.length = 0;
return compileInternal(input, optimize, missingInputsCallback);
};
} else if ('_compileJSONMulti' in Module) {
compilerAcceptsMultipleFiles = true;
compile = Module.cwrap("compileJSONMulti", "string", ["string", "number"]);
} else {
compilerAcceptsMultipleFiles = false;
compile = Module.cwrap("compileJSON", "string", ["string", "number"]);
}
compileJSON = function(source, optimize, cb) {
try {
var result = compile(source, optimize);
} catch (exception) {
result = JSON.stringify({error: 'Uncaught JavaScript exception:\n' + exception});
}
compilationFinished(result, missingInputs);
};
$('#version').text(Module.cwrap("version", "string", [])());
}
previousInput = '';
onChange();
};
var cachedRemoteFiles = {};
function gatherImports(files, importHints, cb) {
importHints = importHints || [];
if (!compilerAcceptsMultipleFiles)
{
cb(files[fileNameFromKey(editor.getCacheFile())]);
return;
}
var importRegex = /^\s*import\s*[\'\"]([^\'\"]+)[\'\"];/g;
var reloop = false;
do {
reloop = false;
for (var fileName in files) {
var match;
while (match = importRegex.exec(files[fileName]))
importHints.push(match[1]);
}
while (importHints.length > 0) {
var m = importHints.pop();
if (m in files) continue;
if (editor.getFiles().indexOf(fileKey(m)) !== -1) {
files[m] = window.localStorage[fileKey(m)];
reloop = true;
} else if (m.startsWith('./') && editor.getFiles().indexOf(fileKey(m.slice(2))) !== -1) {
files[m] = window.localStorage[fileKey(m.slice(2))];
reloop = true;
} else if (m in cachedRemoteFiles) {
files[m] = cachedRemoteFiles[m];
reloop = true;
} else if (githubMatch = /^(https?:\/\/)?(www.)?github.com\/([^\/]*\/[^\/]*)\/(.*)/.exec(m)) {
$('#output').append($('<div/>').append($('<pre/>').text("Loading github.com/" + githubMatch[3] + " ...")));
$.getJSON('https://api.github.com/repos/' + githubMatch[3] + '/contents/' + githubMatch[4], function(result) {
if ('content' in result)
{
var content = Base64.decode(result.content);
cachedRemoteFiles[m] = content;
files[m] = content;
gatherImports(files, importHints, cb);
}
else
cb(null, "Unable to import \"" + m + "\"");
}).fail(function(){
cb(null, "Unable to import \"" + m + "\"");
});
return;
} else {
cb(null, "Unable to import \"" + m + "\"");
return;
}
}
} while (reloop);
cb(JSON.stringify({'sources':files}));
}
var initializeWorker = function() {
if (worker !== null)
worker.terminate();
worker = new Worker('worker.js');
worker.addEventListener('message', function(msg) {
var data = msg.data;
switch (data.cmd) {
case 'versionLoaded':
$('#version').text(data.data);
compilerAcceptsMultipleFiles = !!data.acceptsMultipleFiles;
onCompilerLoaded();
break;
case 'compiled':
compilationFinished(data.data, data.missingInputs);
break;
}
});
worker.onerror = function(msg) { console.log(msg.data); };
worker.addEventListener('error', function(msg) { console.log(msg.data); });
compileJSON = function(source, optimize) {
worker.postMessage({cmd: 'compile', source: source, optimize: optimize});
};
};
var worker = null;
var loadVersion = function(version) {
$('#version').text("(loading)");
queryParams.update({version: version});
var isFirefox = typeof InstallTrigger !== 'undefined';
if (document.location.protocol != 'file:' && Worker !== undefined && isFirefox) {
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
initializeWorker();
worker.postMessage({cmd: 'loadVersion', data: 'https://ethereum.github.io/solc-bin/bin/' + version});
} else {
Module = null;
compileJSON = function(source, optimize) { compilationFinished('{}'); };
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = 'https://ethereum.github.io/solc-bin/bin/' + version;
document.getElementsByTagName("head")[0].appendChild(newScript);
var check = window.setInterval(function() {
if (!Module) return;
window.clearInterval(check);
onCompilerLoaded();
}, 200);
}
};
loadVersion( queryParams.get().version || 'soljson-latest.js');
editor.onChangeSetup(onChange);
document.querySelector('#optimize').addEventListener('change', function(){
queryParams.update({optimize: document.querySelector('#optimize').checked });
compile();
});
// ----------------- compiler output renderer ----------------------
var detailsOpen = {};
@ -682,13 +470,12 @@ var run = function() {
var errLine = parseInt(err[2], 10) - 1;
var errCol = err[4] ? parseInt(err[4], 10) : 0;
if (errFile == '' || errFile == fileNameFromKey(editor.getCacheFile())) {
sourceAnnotations[sourceAnnotations.length] = {
compiler.addAnnotation({
row: errLine,
column: errCol,
text: message,
type: type
};
editor.setAnnotations(sourceAnnotations);
});
}
$error.click(function(ev){
if (errFile != '' && errFile != fileNameFromKey(editor.getCacheFile()) && editor.getFiles().indexOf(fileKey(errFile)) !== -1) {
@ -880,6 +667,50 @@ var run = function() {
return funABI;
};
// ----------------- compiler ----------------------
function handleGithubCall(root, path, cb) {
$('#output').append($('<div/>').append($('<pre/>').text("Loading github.com/" + root + " ...")));
return $.getJSON('https://api.github.com/repos/' + root + '/contents/' + path, cb);
}
var compiler = new Compiler(editor, renderContracts, renderError, errortype, fileNameFromKey, fileKey, handleGithubCall, $('#output'), function() { return hidingRHP; });
function setVersionText(text) {
$('#version').text(text);
}
var loadVersion = function(version) {
setVersionText("(loading)");
queryParams.update({version: version});
var isFirefox = typeof InstallTrigger !== 'undefined';
if (document.location.protocol != 'file:' && Worker !== undefined && isFirefox) {
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
compiler.initializeWorker(version, setVersionText);
} else {
Module = null;
compiler.setCompileJSON()
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = 'https://ethereum.github.io/solc-bin/bin/' + version;
document.getElementsByTagName("head")[0].appendChild(newScript);
var check = window.setInterval(function() {
if (!Module) return;
window.clearInterval(check);
compiler.onCompilerLoaded(setVersionText);
}, 200);
}
};
loadVersion( queryParams.get().version || 'soljson-latest.js');
document.querySelector('#optimize').addEventListener('change', function(){
queryParams.update({optimize: document.querySelector('#optimize').checked });
compiler.compile();
});
storageHandler.sync();
};

@ -0,0 +1,203 @@
var queryParams = require('./query-params');
function Compiler(editor, renderContracts, renderError, errortype, fileNameFromKey, fileKey, handleGithubCall, outputField, hidingRHP) {
var compileJSON;
var compilerAcceptsMultipleFiles;
var previousInput = '';
var sourceAnnotations = [];
var cachedRemoteFiles = {};
var worker = null;
var compileTimeout = null;
function onChange() {
var input = editor.getValue();
if (input === "") {
window.localStorage.setItem(editor.getCacheFile(), '');
return;
}
if (input === previousInput)
return;
previousInput = input;
if (compileTimeout) window.clearTimeout(compileTimeout);
compileTimeout = window.setTimeout(compile, 300);
}
editor.onChangeSetup(onChange);
var compile = function(missingInputs) {
editor.clearAnnotations();
sourceAnnotations = [];
outputField.empty();
var input = editor.getValue();
window.localStorage.setItem(editor.getCacheFile(), input);
var files = {};
files[fileNameFromKey(editor.getCacheFile())] = input;
gatherImports(files, missingInputs, function(input, error) {
outputField.empty();
if (input === null) {
renderError(error);
} else {
var optimize = queryParams.get().optimize;
compileJSON(input, optimize ? 1 : 0);
}
});
};
this.compile = compile;
this.addAnnotation = function(annotation) {
sourceAnnotations[sourceAnnotations.length] = annotation;
editor.setAnnotations(sourceAnnotations);
};
this.setCompileJSON = function() {
compileJSON = function(source, optimize) { compilationFinished('{}'); };
};
function onCompilerLoaded(setVersionText) {
if (worker === null) {
var compile;
var missingInputs = [];
if ('_compileJSONCallback' in Module) {
compilerAcceptsMultipleFiles = true;
var missingInputsCallback = Module.Runtime.addFunction(function(path, contents, error) {
missingInputs.push(Module.Pointer_stringify(path));
});
var compileInternal = Module.cwrap("compileJSONCallback", "string", ["string", "number", "number"]);
compile = function(input, optimize) {
missingInputs.length = 0;
return compileInternal(input, optimize, missingInputsCallback);
};
} else if ('_compileJSONMulti' in Module) {
compilerAcceptsMultipleFiles = true;
compile = Module.cwrap("compileJSONMulti", "string", ["string", "number"]);
} else {
compilerAcceptsMultipleFiles = false;
compile = Module.cwrap("compileJSON", "string", ["string", "number"]);
}
compileJSON = function(source, optimize, cb) {
try {
var result = compile(source, optimize);
} catch (exception) {
result = JSON.stringify({error: 'Uncaught JavaScript exception:\n' + exception});
}
compilationFinished(result, missingInputs);
};
setVersionText(Module.cwrap("version", "string", [])());
}
previousInput = '';
onChange();
};
this.onCompilerLoaded = onCompilerLoaded;
function compilationFinished(result, missingInputs) {
var data;
var noFatalErrors = true; // ie warnings are ok
try {
data = JSON.parse(result);
} catch (exception) {
renderError('Invalid JSON output from the compiler: ' + exception);
return;
}
if (data['error'] !== undefined) {
renderError(data['error']);
if (errortype(data['error']) !== 'warning') noFatalErrors = false;
}
if (data['errors'] != undefined) {
data['errors'].forEach(function(err) {
renderError(err);
if (errortype(err) !== 'warning') noFatalErrors = false;
});
}
if (missingInputs !== undefined && missingInputs.length > 0)
this.compile(missingInputs);
else if (noFatalErrors && !hidingRHP())
renderContracts(data, editor.getValue());
}
this.initializeWorker = function(version, setVersionText) {
if (worker !== null)
worker.terminate();
worker = new Worker('worker.js');
worker.addEventListener('message', function(msg) {
var data = msg.data;
switch (data.cmd) {
case 'versionLoaded':
setVersionText(data.data);
compilerAcceptsMultipleFiles = !!data.acceptsMultipleFiles;
onCompilerLoaded(setVersionText);
break;
case 'compiled':
compilationFinished(data.data, data.missingInputs);
break;
};
});
worker.onerror = function(msg) { console.log(msg.data); };
worker.addEventListener('error', function(msg) { console.log(msg.data); });
compileJSON = function(source, optimize) {
worker.postMessage({cmd: 'compile', source: source, optimize: optimize});
};
worker.postMessage({cmd: 'loadVersion', data: 'https://ethereum.github.io/solc-bin/bin/' + version});
};
function gatherImports(files, importHints, cb) {
importHints = importHints || [];
if (!compilerAcceptsMultipleFiles)
{
cb(files[fileNameFromKey(editor.getCacheFile())]);
return;
}
var importRegex = /^\s*import\s*[\'\"]([^\'\"]+)[\'\"];/g;
var reloop = false;
do {
reloop = false;
for (var fileName in files) {
var match;
while (match = importRegex.exec(files[fileName]))
importHints.push(match[1]);
}
while (importHints.length > 0) {
var m = importHints.pop();
if (m in files) continue;
if (editor.getFiles().indexOf(fileKey(m)) !== -1) {
files[m] = window.localStorage[fileKey(m)];
reloop = true;
} else if (m.startsWith('./') && editor.getFiles().indexOf(fileKey(m.slice(2))) !== -1) {
files[m] = window.localStorage[fileKey(m.slice(2))];
reloop = true;
} else if (m in cachedRemoteFiles) {
files[m] = cachedRemoteFiles[m];
reloop = true;
} else if (githubMatch = /^(https?:\/\/)?(www.)?github.com\/([^\/]*\/[^\/]*)\/(.*)/.exec(m)) {
handleGithubCall(function(result) {
if ('content' in result)
{
var content = Base64.decode(result.content);
cachedRemoteFiles[m] = content;
files[m] = content;
gatherImports(files, importHints, cb);
}
else
cb(null, "Unable to import \"" + m + "\"");
}).fail(function(){
cb(null, "Unable to import \"" + m + "\"");
});
return;
} else {
cb(null, "Unable to import \"" + m + "\"");
return;
}
}
} while (reloop);
cb(JSON.stringify({'sources':files}));
}
}
module.exports = Compiler
Loading…
Cancel
Save