diff options
81 files changed, 1110 insertions, 646 deletions
diff --git a/CODEOWNERS b/CODEOWNERS index d1e7e7673..6abaf69c0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,8 +8,13 @@ static/conformance-view.js @RabsRincon static/cppp-mode.js @RabsRincon static/themes.js @RabsRincon +static/editor.js @RabsRincon +static/compiler.js @RabsRincon lib/asm-docs.js @RabsRincon lib/asm-docs-api.js @RabsRincon +lib/languages.js @RabsRincon +lib/base-compiler.js @RabsRincon +lib/compilers/ @RabsRincon views/sitemap.pug @RabsRincon @@ -21,7 +26,6 @@ lib/compilers/argument-parsers.js @jaredwy static/ast-view.js @TartanLlama static/haskell-mode.js @TartanLlama lib/compilers/haskell.js @TartanLlama -views/example.hs @TartanLlama # cppchedy static/cfg-view.js @cppchedy @@ -72,8 +72,6 @@ lint: $(NODE_MODULES) $(NODE) ./node_modules/.bin/jshint --config etc/jshintrc.server app.js $(shell find lib test -name '*.js') $(NODE) ./node_modules/.bin/jshint --config etc/jshintrc.client $(shell find static -name '*.js' -not -path 'static/ext/*' -not -path static/analytics.js) -LANG:=C++ - node_modules: $(NODE_MODULES) bower_modules: $(BOWER_MODULES) @@ -90,7 +88,7 @@ clean: $(MAKE) -C c-preload clean run: prereqs - $(NODE) ./node_modules/.bin/supervisor -w app.js,lib,etc/config -e 'js|node|properties' --exec $(NODE) $(NODE_ARGS) -- ./app.js --language $(LANG) $(EXTRA_ARGS) + $(NODE) ./node_modules/.bin/supervisor -w app.js,lib,etc/config -e 'js|node|properties' --exec $(NODE) $(NODE_ARGS) -- ./app.js $(EXTRA_ARGS) HASH := $(shell git rev-parse HEAD) dist: prereqs @@ -4,22 +4,14 @@ Compiler Explorer ------------ -Compiler Explorer is an interactive compiler. The left-hand pane shows editable C/C++/Rust/Go/D/Haskell/Swift code. The right, the -assembly output of having compiled the code with a given compiler and settings. Multiple compilers are supported, and +Compiler Explorer is an interactive compiler. The left-hand pane shows editable C, C++, Rust, Go, D, Haskell, Swift and Pascal code. +The right, the assembly output of having compiled the code with a given compiler and settings. Multiple compilers are supported, and the UI layout is configurable (the [Golden Layout](https://www.golden-layout.com/) library is used for this). There is also an ispc compiler for a C variant with extensions for SPMD. -Try out one of the demo sites: [C++][cpp], [Rust][rust], [D][d], [Go][go], [Haskell][haskell], [Swift][swift], [ispc][ispc]. +Try out at [godbolt.org](https://godbolt.org) -[cpp]: https://gcc.godbolt.org/ "Compiler Explorer for C++" -[rust]: https://rust.godbolt.org/ "Compiler Explorer for Rust" -[d]: https://d.godbolt.org/ "Compiler Explorer for D" -[go]: https://go.godbolt.org/ "Compiler Explorer for Go" -[ispc]: https://ispc.godbolt.org/ "Compiler Explorer for ispc" -[haskell]: https://haskell.godbolt.org/ "Compiler Explorer for Haskell" -[swift]: https://swift.godbolt.org/ "Compiler Explorer for Swift" - -You can support this [this project on Patreon](https://patreon.com/mattgodbolt). +You can support [this project on Patreon](https://patreon.com/mattgodbolt). ##### Contact us @@ -47,12 +39,6 @@ Feel free to raise an issue on [github](https://github.com/mattgodbolt/compiler- There's now a [Road map](Roadmap.md) that gives a little insight into future plans for Compiler Explorer. -### Credits - -Compiler Explorer is maintained by [Matt Godbolt](http://xania.org), [Rubén Rincón](https://github.com/RabsRincon) and [Simon Brand](https://blog.tartanllama.xyz/). -Multiple compiler and difference view initially implemented by [Gabriel Devillers](https://github.com/voxelf), -while working for [Kalray](http://www.kalrayinc.com/). Clang optview output by [Jared Wyles](https://github.com/jaredwy). - ### RESTful API There's a simple restful API that can be used to do compiles to asm and to list compilers. In general @@ -65,10 +51,21 @@ future (for the main Compiler Explorer site anyway). The following endpoints are defined: +#### `GET /api/languages` - return a list of languages + +Returns a list of the currently supported languages, as pairs of languages IDs and their names. + #### `GET /api/compilers` - return a list of compilers -Returns a list of compilers. In text form, there's a simple formatting of the ID of the compiler and its -description. In JSON, all the information is returned as an array of compilers, with the `id` key being the +Returns a list of compilers. In text form, there's a simple formatting of the ID of the compiler, its +description and its languge ID. In JSON, all the information is returned as an array of compilers, with the `id` key being the +primary identifier of each compiler. + + +#### `GET /api/compilers/<language-id>` - return a list of compilers with mstching language + +Returns a list of compilers for the provided language id. In text form, there's a simple formatting of the ID of the compiler, its +description and its languge ID. In JSON, all the information is returned as an array of compilers, with the `id` key being the primary identifier of each compiler. #### `POST /api/compiler/<compiler-id>/compile` - perform a compilation @@ -151,3 +148,15 @@ Optional values are marked with a '**' } } ``` + +### Credits + +Compiler Explorer is maintained by [Matt Godbolt](http://xania.org), [Rubén Rincón](https://github.com/RabsRincon) and [Simon Brand](https://blog.tartanllama.xyz/). +- [Gabriel Devillers](https://github.com/voxelf) - Initial multiple compilers and difference view (while working for [Kalray](http://www.kalrayinc.com/)). +- [Jared Wyles](https://github.com/jaredwy) - Clang OPT Output view and maintenance +- [Johan Engelen](https://github.com/JohanEngelen) - D support and its maintenance +- [Chedy Najjar](https://github.com/CppChedy) - CFG View and maintenance +- [Patrick Quist](https://github.com/partouf) - Pascal support +- [Joshua Sheard](https://github.com/jsheard) - ISPC support +- [Marc Poulhiès](https://github.com/dkm) - GCC Dumps view +- [Andrew Pardoe](https://github.com/AndrewPardoe) - WSL-CL support diff --git a/Roadmap.md b/Roadmap.md index 3909046cf..9bbb50444 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -1,9 +1,9 @@ # Compiler Explorer Road Map -CE was started in 2012 to serve my needs at [my company](https:/drw.com) to show how +CE was started in 2012 to serve my needs at [my company](https://drw.com) to show how C++ constructs translated to assembly code. It started out as a `tmux` session with `vi` running in one pane and `watch gcc -S foo.cc -o -` running in the other. Since then, it became a public website -serving the C++, Rust, Go, Haskell, Ispc and D communities and performs around 20,000 compilations per day. +serving the C++, Rust, Go, Haskell, Ispc, D, Swift and Pascal communities and performs around 20,000 compilations per day. This document is an attempt to capture thoughts on the future direction of Compiler Explorer. @@ -53,7 +53,8 @@ development is done on a laptop during a commute (with little or no internet acc ## Priorities -Above all, the priority is to keep the main CE site up, stable and dependable. +Above all, the priority is to keep the main CE site up, stable and dependable. +That also means that URLs should live forever once they are created ## Non-goals CE will remain ad-free, open-source and non-commercial. There's no plans at all to add "freemium" content, @@ -66,6 +67,7 @@ With all this in mind, the tentative goals for 2017 are: - [X] Move to the Monaco editor - [X] Implement diff view - [X] Come up with a decent secure solution for code execution +- [X] Unify every language - [ ] Design an API that can handle remote code execution and download needs - [ ] Implement remote execution UIs @@ -45,7 +45,6 @@ const nopt = require('nopt'), var opts = nopt({ 'env': [String, Array], 'rootDir': [String], - 'language': [String], 'host': [String], 'port': [Number], 'propDebug': [Boolean], @@ -82,7 +81,6 @@ else if (process.env.wsl) { // Set default values for omitted arguments var rootDir = opts.rootDir || './etc'; -var language = opts.language || "C++"; var env = opts.env || ['dev']; var hostname = opts.host; var port = opts.port || 10240; @@ -104,7 +102,6 @@ var fetchCompilersFromRemote = !opts.noRemoteFetch; var propHierarchy = _.flatten([ 'defaults', env, - language, _.map(env, function (e) { return e + '.' + process.platform; }), @@ -126,22 +123,46 @@ const CompileHandler = require('./lib/compile-handler').CompileHandler, // Instantiate a function to access records concerning "compiler-explorer" // in hidden object props.properties -var gccProps = props.propsFor("compiler-explorer"); +var ceProps = props.propsFor("compiler-explorer"); + +const languages = require('./lib/languages').list; // Instantiate a function to access records concerning the chosen language // in hidden object props.properties -var compilerPropsFunc = props.propsFor(language.toLowerCase()); - -// If no option for the compiler ... use gcc's options (??) -function compilerProps(property, defaultValue) { - // My kingdom for ccs... [see Matt's github page] - var forCompiler = compilerPropsFunc(property, undefined); - if (forCompiler !== undefined) return forCompiler; - return gccProps(property, defaultValue); // gccProps comes from lib/compile-handler.js +var compilerPropsFuncsL = {}; +_.each(languages, lang => compilerPropsFuncsL[lang.id] = props.propsFor(lang.id)); + +// Get a property from the specified langId, and if not found, use defaults from CE, +// and at last return whatever default value was set by the caller +function compilerPropsL(lang, property, defaultValue) { + const forLanguage = compilerPropsFuncsL[lang]; + if (forLanguage) { + const forCompiler = forLanguage(property); + if (forCompiler !== undefined) return forCompiler; + } + return ceProps(property, defaultValue); } -var staticMaxAgeSecs = gccProps('staticMaxAgeSecs', 0); -let extraBodyClass = gccProps('extraBodyClass', ''); +// For every lang passed, get its corresponding compiler property +function compilerPropsA(langs, property, defaultValue) { + let forLanguages = {}; + _.each(langs, lang => { + forLanguages[lang.id] = compilerPropsL(lang.id, property, defaultValue); + }); + return forLanguages; +} + +// Same as A version, but transfroms each value by fn(original, lang) +function compilerPropsAT(langs, transform, property, defaultValue) { + let forLanguages = {}; + _.each(langs, lang => { + forLanguages[lang.id] = transform(compilerPropsL(lang.id, property, defaultValue), lang); + }); + return forLanguages; +} + +var staticMaxAgeSecs = ceProps('staticMaxAgeSecs', 0); +let extraBodyClass = ceProps('extraBodyClass', ''); function staticHeaders(res) { if (staticMaxAgeSecs) { @@ -171,73 +192,71 @@ function loadSources() { const fileSources = loadSources(); const clientOptionsHandler = new ClientOptionsHandler(fileSources); -const compileHandler = new CompileHandler(gccProps, compilerProps); +const compileHandler = new CompileHandler(ceProps, compilerPropsL); const ApiHandler = require('./lib/handlers/api').ApiHandler; const apiHandler = new ApiHandler(compileHandler); const SourceHandler = require('./lib/handlers/source').Handler; const sourceHandler = new SourceHandler(fileSources, staticHeaders); function ClientOptionsHandler(fileSources) { - const sources = _.sortBy(fileSources.map(source => ({name: source.name, urlpart: source.urlpart})), "name"); - const languages = _.compact(_.map(gccProps("languages", '').split(':'), thing => { - if (!thing) return null; - var splat = thing.split("="); - return {language: splat[0], url: splat[1]}; - })); - const supportsBinary = !!compilerProps("supportsBinary", true); - const supportsExecute = supportsBinary && !!compilerProps("supportsExecute", true); - const libs = {}; - - const baseLibs = compilerProps("libs"); - - if (baseLibs) { - _.each(baseLibs.split(':'), function (lib) { - libs[lib] = {name: compilerProps('libs.' + lib + '.name')}; - libs[lib].versions = {}; - const listedVersions = compilerProps("libs." + lib + '.versions'); - if (listedVersions) { - _.each(listedVersions.split(':'), function (version) { - libs[lib].versions[version] = {}; - libs[lib].versions[version].version = compilerProps("libs." + lib + '.versions.' + version + '.version'); - libs[lib].versions[version].path = []; - var listedIncludes = compilerProps("libs." + lib + '.versions.' + version + '.path'); - if (listedIncludes) { - _.each(listedIncludes.split(':'), function (path) { - libs[lib].versions[version].path.push(path); - }); - } else { - logger.warn("No paths found for " + lib + " version " + version); - } - }); - } else { - logger.warn("No versions found for " + lib + " library"); - } - }); - } - const options = { - googleAnalyticsAccount: gccProps('clientGoogleAnalyticsAccount', 'UA-55180-6'), - googleAnalyticsEnabled: gccProps('clientGoogleAnalyticsEnabled', false), - sharingEnabled: gccProps('clientSharingEnabled', true), - githubEnabled: gccProps('clientGitHubRibbonEnabled', true), - gapiKey: gccProps('googleApiKey', ''), - googleShortLinkRewrite: gccProps('googleShortLinkRewrite', '').split('|'), - defaultSource: gccProps('defaultSource', ''), - language: language, + const sources = _.sortBy(fileSources.map(function (source) { + return {name: source.name, urlpart: source.urlpart}; + }), "name"); + + var supportsBinary = compilerPropsAT(languages, res => !!res, "supportsBinary", true); + var supportsExecute = supportsBinary && !!compilerPropsAT(languages, (res, lang) => supportsBinary[lang.id] && !!res, "supportsExecute", true); + var libs = {}; + + var baseLibs = compilerPropsA(languages, "libs"); + _.each(baseLibs, function (forLang, lang) { + if (lang && forLang) { + libs[lang] = {}; + _.each(forLang.split(':'), function (lib) { + libs[lang][lib] = {name: compilerPropsL(lang, 'libs.' + lib + '.name')}; + libs[lang][lib].versions = {}; + var listedVersions = compilerPropsL(lang, "libs." + lib + '.versions'); + if (listedVersions) { + _.each(listedVersions.split(':'), function (version) { + libs[lang][lib].versions[version] = {}; + libs[lang][lib].versions[version].version = compilerPropsL(lang, "libs." + lib + '.versions.' + version + '.version'); + libs[lang][lib].versions[version].path = []; + var listedIncludes = compilerPropsL(lang, "libs." + lib + '.versions.' + version + '.path'); + if (listedIncludes) { + _.each(listedIncludes.split(':'), function (path) { + libs[lang][lib].versions[version].path.push(path); + }); + } else { + logger.warn("No paths found for " + lib + " version " + version); + } + }); + } else { + logger.warn("No versions found for " + lib + " library"); + } + }); + } + }); + var options = { + googleAnalyticsAccount: ceProps('clientGoogleAnalyticsAccount', 'UA-55180-6'), + googleAnalyticsEnabled: ceProps('clientGoogleAnalyticsEnabled', false), + sharingEnabled: ceProps('clientSharingEnabled', true), + githubEnabled: ceProps('clientGitHubRibbonEnabled', true), + gapiKey: ceProps('googleApiKey', ''), + googleShortLinkRewrite: ceProps('googleShortLinkRewrite', '').split('|'), + defaultSource: ceProps('defaultSource', ''), compilers: [], libs: libs, - sourceExtension: compilerProps('compileFilename').split('.', 2)[1], - defaultCompiler: compilerProps('defaultCompiler', ''), - compileOptions: compilerProps('defaultOptions', ''), + defaultCompiler: compilerPropsA(languages, 'defaultCompiler', ''), + compileOptions: compilerPropsA(languages, 'defaultOptions', ''), supportsBinary: supportsBinary, supportsExecute: supportsExecute, languages: languages, sources: sources, - raven: gccProps('ravenUrl', ''), + raven: ceProps('ravenUrl', ''), release: gitReleaseName, environment: env, - localStoragePrefix: gccProps('localStoragePrefix'), - cvCompilerCountMax: gccProps('cvCompilerCountMax', 6), - defaultFontScale: gccProps('defaultFontScale', 1.0) + localStoragePrefix: ceProps('localStoragePrefix'), + cvCompilerCountMax: ceProps('cvCompilerCountMax', 6), + defaultFontScale: ceProps('defaultFontScale', 1.0) }; this.setCompilers = function (compilers) { options.compilers = compilers; @@ -278,63 +297,60 @@ function retryPromise(promiseFunc, name, maxFails, retryMs) { } function findCompilers() { - var exes = compilerProps("compilers", "/usr/bin/g++").split(":"); - var ndk = compilerProps('androidNdk'); - if (ndk) { - var toolchains = fs.readdirSync(ndk + "/toolchains"); - toolchains.forEach(function (v, i, a) { - var path = ndk + "/toolchains/" + v + "/prebuilt/linux-x86_64/bin/"; - if (fs.existsSync(path)) { - var cc = fs.readdirSync(path).filter(function (filename) { - return filename.indexOf("g++") !== -1; - }); - a[i] = path + cc[0]; - } else { - a[i] = null; - } - }); - toolchains = toolchains.filter(function (x) { - return x !== null; - }); - exes.push.apply(exes, toolchains); - } + let exes = compilerPropsAT(languages, exs => _.compact(exs.split(":")), "compilers", ""); + + const ndk = compilerPropsA(languages, 'androidNdk'); + _.each(ndk, (ndkPath, langId) => { + if (ndkPath) { + let toolchains = fs.readdirSync(ndkPath + "/toolchains"); + toolchains.forEach((version, index, a) => { + const path = ndkPath + "/toolchains/" + version + "/prebuilt/linux-x86_64/bin/"; + if (fs.existsSync(path)) { + const cc = fs.readdirSync(path).filter(filename => filename.indexOf("g++") !== -1); + a[index] = path + cc[0]; + } else { + a[index] = null; + } + }); + toolchains = toolchains.filter(x => x !== null); + exes[langId].push(toolchains); + } + }); function fetchRemote(host, port, props) { logger.info("Fetching compilers from remote source " + host + ":" + port); - return retryPromise(function () { - return new Promise(function (resolve, reject) { - var request = http.get({ + return retryPromise(() => { + return new Promise((resolve, reject) => { + let request = http.get({ hostname: host, port: port, path: "/api/compilers", headers: { 'Accept': 'application/json' } - }, function (res) { - var str = ''; - res.on('data', function (chunk) { + }, res => { + let str = ''; + res.on('data', chunk => { str += chunk; }); - res.on('end', function () { - var compilers = JSON.parse(str).map(function (compiler) { + res.on('end', () => { + let compilers = JSON.parse(str).map(compiler => { compiler.exe = null; compiler.remote = "http://" + host + ":" + port; return compiler; }); resolve(compilers); }); - }).on('error', function (e) { - reject(e); - }).on('timeout', function () { - reject("timeout"); - }); + }) + .on('error', reject) + .on('timeout', () => reject("timeout")); request.setTimeout(awsProps('proxyTimeout', 1000)); }); }, host + ":" + port, props('proxyRetries', 5), props('proxyRetryMs', 500)) - .catch(function () { + .catch(() => { logger.warn("Unable to contact " + host + ":" + port + "; skipping"); return []; }); @@ -342,10 +358,10 @@ function findCompilers() { function fetchAws() { logger.info("Fetching instances from AWS"); - return awsInstances().then(function (instances) { - return Promise.all(instances.map(function (instance) { + return awsInstances().then(instances => { + return Promise.all(instances.map(instance => { logger.info("Checking instance " + instance.InstanceId); - var address = instance.PrivateDnsName; + let address = instance.PrivateDnsName; if (awsProps("externalTestMode", false)) { address = instance.PublicDnsName; } @@ -354,20 +370,23 @@ function findCompilers() { }); } - function compilerConfigFor(name, parentProps) { - const base = "compiler." + name, - exe = compilerProps(base + ".exe", name); + function compilerConfigFor(langId, compilerName, parentProps) { + const base = "compiler." + compilerName + "."; - function props(name, def) { - return parentProps(base + "." + name, parentProps(name, def)); + function props(propName, def) { + let propsForCompiler = parentProps(langId, base + propName, undefined); + if (propsForCompiler === undefined) { + propsForCompiler = parentProps(langId, propName, def); + } + return propsForCompiler; } - var supportsBinary = !!props("supportsBinary", true); - var supportsExecute = supportsBinary && !!props("supportsExecute", true); - var compilerInfo = { - id: name, - exe: exe, - name: props("name", name), + const supportsBinary = !!props("supportsBinary", true); + const supportsExecute = supportsBinary && !!props("supportsExecute", true); + const compilerInfo = { + id: compilerName, + exe: props("exe", compilerName), + name: props("name", compilerName), alias: props("alias"), options: props("options"), versionFlag: props("versionFlag"), @@ -379,43 +398,68 @@ function findCompilers() { needsMulti: !!props("needsMulti", true), supportsBinary: supportsBinary, supportsExecute: supportsExecute, - postProcess: props("postProcess", "").split("|") + postProcess: props("postProcess", "").split("|"), + lang: langId }; - logger.info("Found compiler", compilerInfo); + logger.debug("Found compiler", compilerInfo); return Promise.resolve(compilerInfo); } - function recurseGetCompilers(name, parentProps) { - if (fetchCompilersFromRemote && name.indexOf("@") !== -1) { - var bits = name.split("@"); - var host = bits[0]; - var port = parseInt(bits[1]); - return fetchRemote(host, port, gccProps); + function recurseGetCompilers(langId, compilerName, parentProps) { + if (fetchCompilersFromRemote && compilerName.indexOf("@") !== -1) { + const bits = compilerName.split("@"); + const host = bits[0]; + const port = parseInt(bits[1]); + return fetchRemote(host, port, ceProps); } - if (name.indexOf("&") === 0) { - var groupName = name.substr(1); + if (compilerName.indexOf("&") === 0) { + const groupName = compilerName.substr(1); - var props = function (name, def) { - if (name === "group") { + const props = function (langId, propName, def) { + if (propName === "group") { return groupName; } - return compilerProps("group." + groupName + "." + name, parentProps(name, def)); + return compilerPropsL(langId, "group." + groupName + "." + propName, parentProps(langId, propName, def)); }; - var exes = props('compilers', '').split(":"); - logger.info("Processing compilers from group " + groupName); - return Promise.all(exes.map(function (compiler) { - return recurseGetCompilers(compiler, props); - })); + const compilerExes = _.compact(props(langId, 'compilers', '').split(":")); + logger.debug("Processing compilers from group " + groupName); + return Promise.all(compilerExes.map(compiler => recurseGetCompilers(langId, compiler, props))); } - if (name === "AWS") return fetchAws(); - return compilerConfigFor(name, parentProps); + if (compilerName === "AWS") return fetchAws(); + return compilerConfigFor(langId, compilerName, parentProps); + } + + function getCompilers() { + let compilers = []; + _.each(exes, (exs, langId) => { + _.each(exs, exe => compilers.push(recurseGetCompilers(langId, exe, compilerPropsL))); + }); + return compilers; + } + + function ensureDistinct(compilers) { + let ids = {}; + _.each(compilers, compiler => { + if (!ids[compiler.id]) ids[compiler.id] = []; + ids[compiler.id].push(compiler); + }); + _.each(ids, (list, id) => { + if (list.length !== 1) { + logger.error(`Compiler ID clash for '${id}' - used by ${ + _.map(list, o => 'lang:' + o.lang + " name:" + o.name).join(', ') + }`); + } + }); + return compilers; } - return Promise.all(exes.map(compiler => recurseGetCompilers(compiler, compilerProps))) + return Promise.all(getCompilers()) .then(_.flatten) - .then(compilers => compileHandler.setCompilers(compilers)) - .then(compilers => _.sortBy(_.compact(compilers), "name")); + .then(compileHandler.setCompilers) + .then(compilers => _.compact(compilers)) + .then(ensureDistinct) + .then(compilers => _.sortBy(compilers, "name")); } function shortUrlHandler(req, res, next) { @@ -426,9 +470,9 @@ function shortUrlHandler(req, res, next) { resolver.resolve(googleUrl) .then(resultObj => { var parsed = url.parse(resultObj.longUrl); - var allowedRe = new RegExp(gccProps('allowedShortUrlHostRe')); + var allowedRe = new RegExp(ceProps('allowedShortUrlHostRe')); if (parsed.host.match(allowedRe) === null) { - logger.warn("Denied access to short URL " + bits[1] + " - linked to " + resultObj.longUrl); + logger.warn(`Denied access to short URL ${bits[1]} - linked to ${resultObj.longUrl}`); return next(); } res.writeHead(301, { @@ -444,7 +488,7 @@ function shortUrlHandler(req, res, next) { } Promise.all([findCompilers(), aws.initConfig(awsProps)]) - .then(function (args) { + .then(args => { let compilers = args[0]; var prevCompilers; @@ -464,7 +508,7 @@ Promise.all([findCompilers(), aws.initConfig(awsProps)]) process.env.NEW_RELIC_NO_CONFIG_FILE = true; process.env.NEW_RELIC_APP_NAME = 'Compiler Explorer'; process.env.NEW_RELIC_LICENSE_KEY = newRelicLicense; - process.env.NEW_RELIC_LABELS = 'Language:' + language; + process.env.NEW_RELIC_LABELS = 'Languages:' + _.map(languages, languages => languages.name); require('newrelic'); logger.info('New relic configured with license', newRelicLicense); } @@ -473,7 +517,7 @@ Promise.all([findCompilers(), aws.initConfig(awsProps)]) if (JSON.stringify(prevCompilers) === JSON.stringify(compilers)) { return; } - logger.info("Compilers:", compilers); + logger.debug("Compilers:", compilers); if (compilers.length === 0) { logger.error("#### No compilers found: no compilation will be done!"); } @@ -484,12 +528,11 @@ Promise.all([findCompilers(), aws.initConfig(awsProps)]) onCompilerChange(compilers); - var rescanCompilerSecs = gccProps('rescanCompilerSecs', 0); + var rescanCompilerSecs = ceProps('rescanCompilerSecs', 0); if (rescanCompilerSecs) { - logger.info("Rescanning compilers every " + rescanCompilerSecs + "secs"); - setInterval(function () { - findCompilers().then(onCompilerChange); - }, rescanCompilerSecs * 1000); + logger.info(`Rescanning compilers every ${rescanCompilerSecs} secs`); + setInterval(() => findCompilers().then(onCompilerChange), + rescanCompilerSecs * 1000); } var webServer = express(), @@ -553,9 +596,9 @@ Promise.all([findCompilers(), aws.initConfig(awsProps)]) webServer.use('/v', express.static(archivedVersions, {maxAge: Infinity, index: false})); } webServer - .use(bodyParser.json({limit: gccProps('bodyParserLimit', '1mb')})) + .use(bodyParser.json({limit: ceProps('bodyParserLimit', '1mb')})) .use(bodyParser.text({ - limit: gccProps('bodyParserLimit', '1mb'), type: function () { + limit: ceProps('bodyParserLimit', '1mb'), type: function () { return true; } })) diff --git a/etc/config/builtin.d.properties b/etc/config/builtin.d.properties deleted file mode 100644 index bfc5e8548..000000000 --- a/etc/config/builtin.d.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/d/ -extensionRe=.*\.d$ diff --git a/etc/config/builtin.default.properties b/etc/config/builtin.default.properties new file mode 100644 index 000000000..0ebf27c6a --- /dev/null +++ b/etc/config/builtin.default.properties @@ -0,0 +1 @@ +sourcePath=./examples/
\ No newline at end of file diff --git a/etc/config/builtin.go.properties b/etc/config/builtin.go.properties deleted file mode 100644 index 1fd2bfefc..000000000 --- a/etc/config/builtin.go.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/go/ -extensionRe=.*\.go$ diff --git a/etc/config/builtin.haskell.properties b/etc/config/builtin.haskell.properties deleted file mode 100644 index c865078c3..000000000 --- a/etc/config/builtin.haskell.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/haskell/ -extensionRe=.*\.hs$ diff --git a/etc/config/builtin.ispc.properties b/etc/config/builtin.ispc.properties deleted file mode 100644 index eff778fcc..000000000 --- a/etc/config/builtin.ispc.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/ispc/ -extensionRe=.*\.ispc$ diff --git a/etc/config/builtin.pascal.properties b/etc/config/builtin.pascal.properties deleted file mode 100644 index 79dea28bb..000000000 --- a/etc/config/builtin.pascal.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/pascal/ -extensionRe=.*\.pas$ diff --git a/etc/config/builtin.rust.properties b/etc/config/builtin.rust.properties deleted file mode 100644 index c61faaa7c..000000000 --- a/etc/config/builtin.rust.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/rust/ -extensionRe=.*\.rs$ diff --git a/etc/config/builtin.swift.properties b/etc/config/builtin.swift.properties deleted file mode 100644 index 77f22f08e..000000000 --- a/etc/config/builtin.swift.properties +++ /dev/null @@ -1,2 +0,0 @@ -sourcepath=./examples/swift/ -extensionRe=.*\.swift$ diff --git a/etc/config/c++.amazon.properties b/etc/config/c++.amazon.properties index 2db61d026..f04bcded9 100644 --- a/etc/config/c++.amazon.properties +++ b/etc/config/c++.amazon.properties @@ -1,6 +1,5 @@ compilers=&gcc86:&icc:&clang:&cl:&cross:&ellcc:&zapcc defaultCompiler=g72 -textBanner=Compilation provided by Compiler Explorer at https://gcc.godbolt.org/ demangler=/opt/compiler-explorer/gcc-7.2.0/bin/c++filt objdumper=/opt/compiler-explorer/gcc-7.2.0/bin/objdump diff --git a/etc/config/c++.cppx.properties b/etc/config/c++.cppx.properties index 8b2e7c7d1..fc1062552 100644 --- a/etc/config/c++.cppx.properties +++ b/etc/config/c++.cppx.properties @@ -1,6 +1,5 @@ compilers=cppx010:cppx_trunk defaultCompiler=cppx010 -textBanner=Compilation provided by Compiler Explorer at https://cppx.godbolt.org/ intelAsm=-mllvm --x86-asm-syntax=intel demangler=/opt/compiler-explorer/gcc-snapshot/bin/c++filt objdumper=/opt/compiler-explorer/gcc-snapshot/bin/objdump diff --git a/etc/config/c++.danger.properties b/etc/config/c++.danger.properties index 2ac38ef8d..52592830a 100644 --- a/etc/config/c++.danger.properties +++ b/etc/config/c++.danger.properties @@ -1,6 +1,5 @@ # Matt's home development computer defaultCompiler=g54 -textBanner=Testing the text banner compilers=g54:&clang:&arm:avrg454:&cl #compilers=g54:g47:g48:avr #compilers=localhost@20480 diff --git a/etc/config/c++.defaults.properties b/etc/config/c++.defaults.properties index 921dc2b1a..be167d937 100644 --- a/etc/config/c++.defaults.properties +++ b/etc/config/c++.defaults.properties @@ -2,7 +2,6 @@ # AP: Add &cl group compilers=/usr/bin/g++-4.4:/usr/bin/g++-4.5:/usr/bin/g++-4.6:/usr/bin/g++-4.7:/usr/bin/g++-4.8:/usr/bin/clang++:/usr/bin/g++-5:/usr/bin/g++-7:/usr/bin/g++-6:/usr/bin/g++:&cl defaultCompiler=/usr/bin/g++ -compileFilename=example.cpp postProcess= demangler=c++filt objdumper=objdump diff --git a/etc/config/c.amazon.properties b/etc/config/c.amazon.properties new file mode 100644 index 000000000..187310ebf --- /dev/null +++ b/etc/config/c.amazon.properties @@ -0,0 +1,6 @@ +# Amazon settings for C +compilers= +defaultCompiler= +demangler=/opt/compiler-explorer/gcc-7.2.0/bin/c++filt +objdumper=/opt/compiler-explorer/gcc-7.2.0/bin/objdump +group..compilers= diff --git a/etc/config/c.defaults.properties b/etc/config/c.defaults.properties new file mode 100644 index 000000000..d38465abb --- /dev/null +++ b/etc/config/c.defaults.properties @@ -0,0 +1,10 @@ +# Default settings for C +compilers=/usr/bin/gcc-4.4:/usr/bin/gcc-4.5:/usr/bin/gcc-4.6:/usr/bin/gcc-4.7:/usr/bin/gcc-4.8:/usr/bin/clangcc:/usr/bin/gcc-5:/usr/bin/gcc-7:/usr/bin/gcc-6:/usr/bin/gcc +defaultCompiler=/usr/bin/gcc +postProcess= +demangler=c++filt +objdumper=objdump +supportsBinary=true +binaryHideFuncRe=^(__.*|_(init|start|fini)|(de)?register_tm_clones|call_gmon_start|frame_dummy|\.plt.*)$ +stubRe=\bmain\b +stubText=int main(void){return 0;/*stub provided by Compiler Explorer*/} diff --git a/etc/config/compiler-explorer.defaults.properties b/etc/config/compiler-explorer.defaults.properties index 9fd2a8f8d..3f54a3bac 100644 --- a/etc/config/compiler-explorer.defaults.properties +++ b/etc/config/compiler-explorer.defaults.properties @@ -15,3 +15,5 @@ googleShortLinkRewrite=^https?://goo.gl/(.*)$|https://godbolt.org/g/$1 wine=/opt/wine-devel/bin/wine64 cvCompilerCountMax=6 + +textBanner=Compilation provided by Compiler Explorer at https://godbolt.org/ diff --git a/etc/config/compiler-explorer.wud-mgodbolt01.properties b/etc/config/compiler-explorer.wud-mgodbolt01.properties index 1b0954c13..6840bb06f 100644 --- a/etc/config/compiler-explorer.wud-mgodbolt01.properties +++ b/etc/config/compiler-explorer.wud-mgodbolt01.properties @@ -7,7 +7,6 @@ clientSharingEnabled=false #compiler.r100.exe=/mnt/data/apps/rust/bin/rustc #compiler.r100.name=rustc 1.0.0 #compiler.r100.intelAsm=-Cllvm-args=--x86-asm-syntax=intel -#compileFilename=example.rs #cacheMb=100 #language=C++ #clientGoogleAnalyticsEnabled=true diff --git a/etc/config/cppx.amazon.properties b/etc/config/cppx.amazon.properties new file mode 100644 index 000000000..fc1062552 --- /dev/null +++ b/etc/config/cppx.amazon.properties @@ -0,0 +1,13 @@ +compilers=cppx010:cppx_trunk +defaultCompiler=cppx010 +intelAsm=-mllvm --x86-asm-syntax=intel +demangler=/opt/compiler-explorer/gcc-snapshot/bin/c++filt +objdumper=/opt/compiler-explorer/gcc-snapshot/bin/objdump + +compiler.cppx010.exe=/opt/compiler-explorer/cppx/current/bin/clang++ +compiler.cppx010.name=Runs Herb's examples +compiler.cppx010.options=--gcc-toolchain=/opt/compiler-explorer/gcc-snapshot -std=c++1z -Xclang -freflection -I/opt/compiler-explorer/cppx/current/include -stdlib=libc++ -include cppx/meta -include cppx/compiler + +compiler.cppx_trunk.exe=/opt/compiler-explorer/clang-cppx-trunk/bin/clang++ +compiler.cppx_trunk.name=Latest trunk +compiler.cppx_trunk.options=--gcc-toolchain=/opt/compiler-explorer/gcc-snapshot -std=c++1z -Xclang -freflection -I/opt/compiler-explorer/clang-cppx-trunk/include -stdlib=libc++ -include cppx/meta -include cppx/compiler diff --git a/etc/config/cppx.defaults.properties b/etc/config/cppx.defaults.properties new file mode 100644 index 000000000..7125b96fb --- /dev/null +++ b/etc/config/cppx.defaults.properties @@ -0,0 +1,9 @@ +# Default settings for cppx +postProcess= +demangler=c++filt +objdumper=objdump +options= +supportsBinary=true +binaryHideFuncRe=^(__.*|_(init|start|fini)|(de)?register_tm_clones|call_gmon_start|frame_dummy|\.plt.*)$ +stubRe=\bmain\b +stubText=int main(void){return 0;/*stub provided by Compiler Explorer*/} diff --git a/etc/config/d.amazon.properties b/etc/config/d.amazon.properties index e09076628..9131f6389 100644 --- a/etc/config/d.amazon.properties +++ b/etc/config/d.amazon.properties @@ -1,5 +1,4 @@ compilers=gdc48:gdc49:gdc52:&ldc -textBanner=Compilation provided by Compiler Explorer at https://d.godbolt.org/ defaultCompiler=ldc160 objdumper=/opt/compiler-explorer/gcc-7.2.0/bin/objdump diff --git a/etc/config/d.defaults.properties b/etc/config/d.defaults.properties index 4134b80f9..6f797c16a 100644 --- a/etc/config/d.defaults.properties +++ b/etc/config/d.defaults.properties @@ -1,5 +1,4 @@ compilers=/usr/bin/gdc:/usr/bin/gdc-4.4:/usr/bin/gdc-4.6 -compileFilename=example.d demangler=/opt/compiler-explorer/ldc1.5.0/ldc2-1.5.0-linux-x86_64/bin/ddemangle supportsBinary=true objdumper=objdump diff --git a/etc/config/go.amazon.properties b/etc/config/go.amazon.properties index f539d2463..94fe625a1 100644 --- a/etc/config/go.amazon.properties +++ b/etc/config/go.amazon.properties @@ -1,7 +1,5 @@ defaultCompiler=gl192 -textBanner=Compilation provided by Compiler Explorer at https://go.godbolt.org/ objdumper=/opt/compiler-explorer/gcc-7.2.0/bin/objdump -compileFilename=file.go compilers=gccgo494:gccgo630:gccgo720:&gl group.gl.compilers=6g141:gl172:gl185:gl192 group.gl.versionFlag=version diff --git a/etc/config/go.defaults.properties b/etc/config/go.defaults.properties index 12db27f7a..e8856b3ee 100644 --- a/etc/config/go.defaults.properties +++ b/etc/config/go.defaults.properties @@ -1,4 +1,3 @@ -compileFilename=file.go supportsBinary=true objdumper=objdump binaryHideFuncRe=^(_.*|(de)?register_tm_clones|frame_dummy|.*@plt)$ diff --git a/etc/config/go.lud-mgodbolt01.properties b/etc/config/go.lud-mgodbolt01.properties index 845756ab9..92ddd917b 100644 --- a/etc/config/go.lud-mgodbolt01.properties +++ b/etc/config/go.lud-mgodbolt01.properties @@ -1,8 +1,7 @@ defaultCompiler=6g compilers=6g -compileFilename=file.go compiler.6g.exe=/home/mgodbolt/apps/go/go/pkg/tool/linux_amd64/6g compiler.6g.name=Yah compiler.6g.versionFlag=-V -compiler.6g.compilerType=6g +compiler.6g.compilerType=golang compiler.6g.supportsBinary=false diff --git a/etc/config/haskell.defaults.properties b/etc/config/haskell.defaults.properties index a63fe019e..843e9abc3 100644 --- a/etc/config/haskell.defaults.properties +++ b/etc/config/haskell.defaults.properties @@ -1,5 +1,4 @@ compilers=/usr/bin/ghc -compileFilename=example.hs supportsBinary=false compilerType=haskell postProcess=haskell/demangle diff --git a/etc/config/ispc.defaults.properties b/etc/config/ispc.defaults.properties index ad57dcd9a..5e4bafb75 100644 --- a/etc/config/ispc.defaults.properties +++ b/etc/config/ispc.defaults.properties @@ -1,5 +1,4 @@ compilers=ispc -compileFilename=example.ispc supportsBinary=false compilerType=ispc diff --git a/etc/config/pascal.defaults.properties b/etc/config/pascal.defaults.properties index 6c1b9b302..c5d24cd31 100644 --- a/etc/config/pascal.defaults.properties +++ b/etc/config/pascal.defaults.properties @@ -1,7 +1,6 @@ # Default settings for Pascal compilers=/usr/bin/fpc defaultCompiler=/usr/bin/fpc -compileFilename=output.pas postProcess= demangler=/dev/null objdumper=objdump diff --git a/etc/config/rust.amazon.properties b/etc/config/rust.amazon.properties index f50b34b0a..8aa40d1ba 100644 --- a/etc/config/rust.amazon.properties +++ b/etc/config/rust.amazon.properties @@ -1,5 +1,4 @@ compilers=&rust -textBanner=Compilation provided by Compiler Explorer at https://rust.godbolt.org/ objdumper=/opt/compiler-explorer/gcc-7.2.0/bin/objdump defaultCompiler=r1210 group.rust.compilers=r1210:r1200:r1190:r1180:r1170:r1160:r1151:r1140:r1130:r1120:r1110:r1100:r190:r180:r170:r160:r150:r140:r130:r120:r110:r100:nightly:beta diff --git a/etc/config/rust.defaults.properties b/etc/config/rust.defaults.properties index 722c2d51a..929da01aa 100644 --- a/etc/config/rust.defaults.properties +++ b/etc/config/rust.defaults.properties @@ -1,5 +1,4 @@ compilers=/usr/local/bin/rustc -compileFilename=example.rs supportsBinary=false compilerType=rust demangler=rust/bin/rustfilt diff --git a/etc/config/swift.defaults.properties b/etc/config/swift.defaults.properties index 56392e019..fcf7f9370 100644 --- a/etc/config/swift.defaults.properties +++ b/etc/config/swift.defaults.properties @@ -1,4 +1,4 @@ -compileFilename=example.swift +compilers= compileToAsm=-emit-assembly supportsBinary=false compilerType=swift diff --git a/etc/scripts/pre-commit b/etc/scripts/pre-commit index c349b8af9..96b4f6834 100755 --- a/etc/scripts/pre-commit +++ b/etc/scripts/pre-commit @@ -1,5 +1,4 @@ #!/bin/bash - FORBIDDEN='console.log' FILES_PATTERN='\.(js)(\..+)?$' diff --git a/views/example.cpp b/examples/c++/default.cpp index 86b3a4a3d..e8c4fe4ec 100644 --- a/views/example.cpp +++ b/examples/c++/default.cpp @@ -1,4 +1,4 @@ // Type your code here, or load an example. int square(int num) { return num * num; -} +}
\ No newline at end of file diff --git a/examples/c/Max_array.c b/examples/c/Max_array.c new file mode 100644 index 000000000..bc2ef8799 --- /dev/null +++ b/examples/c/Max_array.c @@ -0,0 +1,5 @@ +void maxArray(double* x, double* y) { + for (int i = 0; i < 65536; i++) { + if (y[i] > x[i]) x[i] = y[i]; + } +} diff --git a/examples/c/Sum_over_array.c b/examples/c/Sum_over_array.c new file mode 100644 index 000000000..601ef1600 --- /dev/null +++ b/examples/c/Sum_over_array.c @@ -0,0 +1,7 @@ +int testFunction(int* input, int length) { + int sum = 0; + for (int i = 0; i < length; ++i) { + sum += input[i]; + } + return sum; +} diff --git a/examples/c/default.c b/examples/c/default.c new file mode 100644 index 000000000..e8c4fe4ec --- /dev/null +++ b/examples/c/default.c @@ -0,0 +1,4 @@ +// Type your code here, or load an example. +int square(int num) { + return num * num; +}
\ No newline at end of file diff --git a/examples/cppx/Interface.cpp b/examples/cppx/Interface.cpp new file mode 100644 index 000000000..1af545545 --- /dev/null +++ b/examples/cppx/Interface.cpp @@ -0,0 +1,61 @@ + +//==================================================================== +// Library code: implementing the metaclass (once) + +$class interface { + constexpr { + compiler.require($interface.variables().empty(), + "interfaces may not contain data"); + for... (auto f : $interface.functions()) { + compiler.require(!f.is_copy() && !f.is_move(), + "interfaces may not copy or move; consider a" + " virtual clone() instead"); + if (!f.has_access()) f.make_public(); + compiler.require(f.is_public(), + "interface functions must be public"); + f.make_pure_virtual(); + } + } + virtual ~interface() noexcept { } +}; + + +//==================================================================== +// User code: using the metaclass to write a type (many times) + +interface Shape { + int area() const; + void scale_by(double factor); +}; + + // try putting any of these lines into Shape to see "interface" rules + // enforced => using the metaclass name to declare intent makes + // this code more robust to such changes under maintenance + // + // int i; // error: interfaces may not contain data + // private: void g(); // error: interface functions must be public + // Shape(const Shape&); // error: interfaces may not copy or move; + // // consider a virtual clone() instead + +// Godbolt.org note: Click the "triangle ! icon" to see the output +constexpr { + compiler.debug($Shape); +} + + +//==================================================================== +// And then continue to use it as "just a class" as always... this is +// normal code just as if we'd written Shape not using a metaclass + +class Circle : public Shape { +public: + int area() const override { return 1; } + void scale_by(double factor) override { } +}; + +#include <memory> + +int main() { + std::unique_ptr<Shape> shape = std::make_unique<Circle>(); + shape->area(); +} diff --git a/views/example.cppx b/examples/cppx/default.cpp index 503cffe41..503cffe41 100644 --- a/views/example.cppx +++ b/examples/cppx/default.cpp diff --git a/views/example.d b/examples/d/default.d index 3cb5a30c3..3cb5a30c3 100644 --- a/views/example.d +++ b/examples/d/default.d diff --git a/views/example.go b/examples/go/default.go index 65be52b7a..65be52b7a 100644 --- a/views/example.go +++ b/examples/go/default.go diff --git a/views/example.hs b/examples/haskell/default.hs index 97106284d..97106284d 100644 --- a/views/example.hs +++ b/examples/haskell/default.hs diff --git a/views/example.ispc b/examples/ispc/default.ispc index 0924dc0b6..0924dc0b6 100644 --- a/views/example.ispc +++ b/examples/ispc/default.ispc diff --git a/examples/pascal/max_array.pas b/examples/pascal/Max_array.pas index ebacc2b3b..ebacc2b3b 100644 --- a/examples/pascal/max_array.pas +++ b/examples/pascal/Max_array.pas diff --git a/examples/pascal/sum_over_array.pas b/examples/pascal/Sum_over_array.pas index 714a4f6c9..714a4f6c9 100644 --- a/examples/pascal/sum_over_array.pas +++ b/examples/pascal/Sum_over_array.pas diff --git a/views/example.pas b/examples/pascal/default.pas index eddb3978a..eddb3978a 100644 --- a/views/example.pas +++ b/examples/pascal/default.pas diff --git a/views/example.rs b/examples/rust/default.rs index f224705c9..ff272bd4d 100644 --- a/views/example.rs +++ b/examples/rust/default.rs @@ -1,4 +1,4 @@ // Type your code here, or load an example. -pub fn square(num: i32) -> i32 { +pub fn square(num: i32) -> i32 { num * num } diff --git a/views/example.swift b/examples/swift/default.swift index af4a0979c..af4a0979c 100644 --- a/views/example.swift +++ b/examples/swift/default.swift diff --git a/lib/base-compiler.js b/lib/base-compiler.js index 21c60f28f..3788aea22 100644 --- a/lib/base-compiler.js +++ b/lib/base-compiler.js @@ -34,12 +34,20 @@ const child_process = require('child_process'), logger = require('./logger').logger, compilerOptInfo = require("compiler-opt-info"), argumentParsers = require("./compilers/argument-parsers"), - cfg = require('./cfg'); + cfg = require('./cfg'), + languages = require('./languages').list; -function Compile(compiler, env) { +function Compile(compiler, env, langId) { this.compiler = compiler; + this.lang = langId; + this.langInfo = languages[langId]; + if (!this.langInfo) { + throw new Error("Missing language info for " + langId); + } + this.compileFilename = 'example' + this.langInfo.extensions[0]; this.env = env; - this.asm = new asm.AsmParser(env.compilerProps); + this.compilerProps = _.partial(this.env.compilerPropsL, this.lang); + this.asm = new asm.AsmParser(this.compilerProps); this.compiler.supportsIntel = !!this.compiler.intelAsm; } @@ -75,10 +83,10 @@ Compile.prototype.exec = function (compiler, args, options) { Compile.prototype.getDefaultExecOptions = function () { return { - timeoutMs: this.env.gccProps("compileTimeoutMs", 100), - maxErrorOutput: this.env.gccProps("max-error-output", 5000), + timeoutMs: this.env.ceProps("compileTimeoutMs", 100), + maxErrorOutput: this.env.ceProps("max-error-output", 5000), env: this.env.getEnv(this.compiler.needsMulti), - wrapper: this.env.compilerProps("compiler-wrapper") + wrapper: this.compilerProps("compiler-wrapper") }; }; @@ -226,13 +234,13 @@ Compile.prototype.compile = function (source, options, backendOptions, filters) return Promise.resolve(cached); } - if (filters.binary && !source.match(this.env.compilerProps("stubRe"))) { - source += "\n" + this.env.compilerProps("stubText") + "\n"; + if (filters.binary && !source.match(this.compilerProps("stubRe"))) { + source += "\n" + this.compilerProps("stubText") + "\n"; } return this.env.enqueue(() => { const tempFileAndDirPromise = this.newTempDir() .then(dirPath => { - const inputFilename = path.join(dirPath, this.env.compilerProps("compileFilename")); + const inputFilename = path.join(dirPath, this.compileFilename); return this.writeFile(inputFilename, source).then(() => ({ inputFilename: inputFilename, dirPath: dirPath @@ -357,8 +365,7 @@ Compile.prototype.postProcessAsm = function (result) { }; Compile.prototype.processOptOutput = function (hasOptOutput, optPath) { - const output = []; - const inputFile = this.env.compilerProps("compileFilename", ""); + let output = []; return new Promise( resolve => { fs.createReadStream(optPath, {encoding: "utf-8"}) @@ -366,7 +373,7 @@ Compile.prototype.processOptOutput = function (hasOptOutput, optPath) { .on("data", opt => { if (opt.DebugLoc && opt.DebugLoc.File && - opt.DebugLoc.File.indexOf(inputFile) > -1) { + opt.DebugLoc.File.indexOf(this.compileFilename) > -1) { output.push(opt); } @@ -519,7 +526,7 @@ Compile.prototype.processGccDumpOutput = function (opts, result) { Compile.prototype.postProcess = function (result, outputFilename, filters) { const postProcess = _.compact(this.compiler.postProcess); - const maxSize = this.env.gccProps("max-asm-size", 8 * 1024 * 1024); + const maxSize = this.env.ceProps("max-asm-size", 8 * 1024 * 1024); let optPromise, asmPromise, execPromise; if (result.hasOptOutput) { optPromise = this.processOptOutput(result.hasOptOutput, result.optPath); @@ -555,7 +562,7 @@ Compile.prototype.postProcess = function (result, outputFilename, filters) { ); } if (filters.execute) { - const maxExecOutputSize = this.env.gccProps("max-executable-output-size", 32 * 1024); + const maxExecOutputSize = this.env.ceProps("max-executable-output-size", 32 * 1024); execPromise = this.execBinary(outputFilename, result, maxExecOutputSize); } else { execPromise = Promise.resolve(""); @@ -635,7 +642,7 @@ Compile.prototype.initialise = function () { 'with re', versionRe); return null; } - logger.info(compiler + " is version '" + version + "'"); + logger.debug(compiler + " is version '" + version + "'"); this.compiler.version = version; return argumentParser(this); }, diff --git a/lib/compilation-env.js b/lib/compilation-env.js index 81f21e54a..1d2d7b6cb 100644 --- a/lib/compilation-env.js +++ b/lib/compilation-env.js @@ -32,20 +32,18 @@ const LRU = require('lru-cache'), Queue.configure(Promise); -function CompilationEnvironment(gccProps, compilerProps) { - this.gccProps = gccProps; - this.compilerProps = compilerProps; - this.okOptions = new RegExp(gccProps('optionsWhitelistRe', '.*')); - this.badOptions = new RegExp(gccProps('optionsBlacklistRe', '(?!)')); +function CompilationEnvironment(ceProps, compilerPropsL) { + this.ceProps = ceProps; + this.compilerPropsL = compilerPropsL; + this.okOptions = new RegExp(ceProps('optionsWhitelistRe', '.*')); + this.badOptions = new RegExp(ceProps('optionsBlacklistRe', '(?!)')); this.cache = LRU({ - max: gccProps('cacheMb') * 1024 * 1024, - length: function (n) { - return JSON.stringify(n).length; - } + max: ceProps('cacheMb') * 1024 * 1024, + length: n => JSON.stringify(n).length }); this.cacheHits = 0; this.cacheMisses = 0; - this.compileQueue = new Queue(gccProps("maxConcurrentCompiles", 1), Infinity); + this.compileQueue = new Queue(ceProps("maxConcurrentCompiles", 1), Infinity); this.multiarch = null; try { var multi = child_process.execSync("gcc -print-multiarch").toString().trim(); diff --git a/lib/compile-handler.js b/lib/compile-handler.js index 1cbde43f4..159c5eeb5 100644 --- a/lib/compile-handler.js +++ b/lib/compile-handler.js @@ -22,8 +22,7 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -var child_process = require('child_process'), - temp = require('temp'), +const temp = require('temp'), fs = require('fs'), path = require('path'), httpProxy = require('http-proxy'), @@ -37,12 +36,12 @@ var child_process = require('child_process'), temp.track(); -var oneTimeInit = false; +let oneTimeInit = false; -function initialise(gccProps, compilerEnv) { +function initialise(ceProps, compilerEnv) { if (oneTimeInit) return; oneTimeInit = true; - var tempDirCleanupSecs = gccProps("tempDirCleanupSecs", 600); + const tempDirCleanupSecs = ceProps("tempDirCleanupSecs", 600); logger.info("Cleaning temp dirs every " + tempDirCleanupSecs + " secs"); setInterval(function () { if (compilerEnv.isBusy()) { @@ -56,71 +55,61 @@ function initialise(gccProps, compilerEnv) { }, tempDirCleanupSecs * 1000); } -function CompileHandler(gccProps, compilerProps) { +function CompileHandler(ceProps, compilerPropsL) { + const self = this; this.compilersById = {}; - this.compilerEnv = new CompilationEnvironment(gccProps, compilerProps); - initialise(gccProps, this.compilerEnv); + this.compilerEnv = new CompilationEnvironment(ceProps, compilerPropsL); + initialise(ceProps, this.compilerEnv); this.factories = {}; this.stat = Promise.denodeify(fs.stat); + this.proxy = httpProxy.createProxyServer({}); + this.textBanner = ceProps('textBanner'); + this.create = function (compiler) { - var type = compiler.compilerType || "default"; - if (this.factories[type] === undefined) { - var compilerPath = './compilers/' + type; - logger.info("Loading compiler from", compilerPath); - this.factories[type] = require(compilerPath); + const type = compiler.compilerType || "default"; + if (self.factories[type] === undefined) { + const compilerPath = './compilers/' + type; + logger.debug("Loading compiler from", compilerPath); + self.factories[type] = require(compilerPath); } if (path.isAbsolute(compiler.exe)) { // Try stat'ing the compiler to cache its mtime and only re-run it if it // has changed since the last time. - return this.stat(compiler.exe) - .then(_.bind(function (res) { - var cached = this.compilersById[compiler.id]; + return self.stat(compiler.exe) + .then(_.bind(res => { + const cached = self.findCompiler(compiler.lang, compiler.id); if (cached && cached.mtime.getTime() === res.mtime.getTime()) { logger.debug(compiler.id + " is unchanged"); return cached; } - return this.factories[type](compiler, this.compilerEnv).then(function (compiler) { + return self.factories[type](compiler, self.compilerEnv, compiler.lang).then(compiler => { compiler.mtime = res.mtime; return compiler; }); - }, this)) - .catch(function (err) { + }, self)) + .catch(err => { logger.warn("Unable to stat compiler binary", err); return null; }); } else { - return this.factories[type](compiler, this.compilerEnv); + return self.factories[type](compiler, self.compilerEnv, compiler.lang); } }; - - this.setCompilers = function (compilers) { - return Promise.all(_.map(compilers, this.create, this)) - .then(_.compact) - .then(compilers => { - _.each(compilers, compiler => this.compilersById[compiler.compiler.id] = compiler); - return _.map(compilers, compiler => compiler.getInfo()); - }) - .catch(err => logger.error(err)); - }; - var proxy = httpProxy.createProxyServer({}); - var textBanner = compilerProps('textBanner'); - - this.handler = _.bind(function compile(req, res, next) { - var source, options, backendOptions, filters, compiler; + this.handler = function compile(req, res, next) { + let source, options, backendOptions, filters, + compiler = self.findCompiler(req.lang || req.body.lang, req.compiler || req.body.compiler); + if (!compiler) return next(); if (req.is('json')) { // JSON-style request - compiler = this.compilersById[req.compiler || req.body.compiler]; - if (!compiler) return next(); - var requestOptions = req.body.options; + const requestOptions = req.body.options; source = req.body.source; options = requestOptions.userArguments; backendOptions = requestOptions.compilerOptions; filters = requestOptions.filters || compiler.getDefaultFilters(); } else { // API-style - compiler = this.compilersById[req.compiler]; - if (!compiler) return next(); + // Find the compiler the user is interested in... source = req.body; options = req.query.options; // By default we get the default filters. @@ -140,10 +129,10 @@ function CompileHandler(gccProps, compilerProps) { delete filters[filter]; }); } - var remote = compiler.getRemote(); + const remote = compiler.getRemote(); if (remote) { req.url = req.originalUrl; // Undo any routing that was done to get here (i.e. /api/* path has been removed) - proxy.web(req, res, {target: remote}, function (e) { + self.proxy.web(req, res, {target: remote}, function (e) { logger.error("Proxy error: ", e); next(e); }); @@ -170,7 +159,7 @@ function CompileHandler(gccProps, compilerProps) { } else { res.set('Content-Type', 'text/plain'); try { - if (!_.isEmpty(textBanner)) res.write('# ' + textBanner + "\n"); + if (!_.isEmpty(self.textBanner)) res.write('# ' + self.textBanner + "\n"); res.write(textify(result.asm)); if (result.code !== 0) res.write("\n# Compiler exited with result code " + result.code); if (!_.isEmpty(result.stdout)) res.write("\nStandard out:\n" + textify(result.stdout)); @@ -198,7 +187,39 @@ function CompileHandler(gccProps, compilerProps) { res.end(JSON.stringify({code: -1, stderr: [{text: error}]})); } ); - }, this); + }; + this.setCompilers = function (newCompilers) { + // Delete every compiler first... + self.compilersById = {}; + return Promise.all(_.map(newCompilers, self.create)) + .then(_.compact) + .then(compilers => { + _.each(compilers, compiler => { + const langId = compiler.compiler.lang; + if (!self.compilersById[langId]) self.compilersById[langId] = {}; + self.compilersById[langId][compiler.compiler.id] = compiler; + }, self); + return _.map(compilers,compiler => compiler.getInfo()); + }) + .catch(logger.error); + }; + this.findCompiler = function (langId, compilerId) { + if (langId && self.compilersById[langId]) { + return self.compilersById[langId][compilerId]; + } + // If the lang is bad, try to find it in every language + let response; + _.each(self.compilersById, compilerInLang => { + if (response === undefined) { + _.each(compilerInLang, compiler => { + if (response === undefined && compiler.compiler.id === compilerId) { + response = compiler; + } + }); + } + }); + return response; + }; } module.exports = { diff --git a/lib/compilers/WSL-CL.js b/lib/compilers/WSL-CL.js index 4582e546b..be3bc21c4 100644 --- a/lib/compilers/WSL-CL.js +++ b/lib/compilers/WSL-CL.js @@ -32,9 +32,9 @@ const Compile = require('../base-compiler'), asm = require('../asm-cl'), temp = require('temp'); -function compileCl(info, env) { - const compile = new Compile(info, env); - compile.asm = new asm.AsmParser(env.compilerProps); +function compileCl(info, env, langId) { + var compile = new Compile(info, env, langId); + compile.asm = new asm.AsmParser(compile.compilerProps); info.supportsFiltersInBinary = true; if (process.platform === "linux") { const origExec = compile.exec; diff --git a/lib/compilers/Wine-CL.js b/lib/compilers/Wine-CL.js index 0e92fe4de..d206bb0f2 100644 --- a/lib/compilers/Wine-CL.js +++ b/lib/compilers/Wine-CL.js @@ -25,12 +25,12 @@ var Compile = require('../base-compiler'); var asm = require('../asm-cl'); -function compileCl(info, env) { - var compile = new Compile(info, env); - compile.asm = new asm.AsmParser(env.compilerProps); +function compileCl(info, env, langId) { + var compile = new Compile(info, env, langId); + compile.asm = new asm.AsmParser(compile.compilerProps); info.supportsFiltersInBinary = true; if (process.platform == "linux") { - var wine = env.gccProps("wine"); + var wine = env.ceProps("wine"); var origExec = compile.exec; compile.exec = function (command, args, options) { if (command.toLowerCase().endsWith(".exe")) { diff --git a/lib/compilers/default.js b/lib/compilers/default.js index 759d2bbe8..64a542760 100644 --- a/lib/compilers/default.js +++ b/lib/compilers/default.js @@ -24,7 +24,7 @@ const Compile = require('../base-compiler'); -module.exports = function (info, env) { - var comp = new Compile(info, env); +module.exports = function (info, env, langId) { + var comp = new Compile(info, env, langId); return comp.initialise(); };
\ No newline at end of file diff --git a/lib/compilers/golang.js b/lib/compilers/golang.js index 18a299f17..df63bc8bf 100644 --- a/lib/compilers/golang.js +++ b/lib/compilers/golang.js @@ -25,8 +25,8 @@ const Compile = require('../base-compiler'), _ = require('underscore-node'); -function compilenewgol(info, env) { - const compiler = new Compile(info, env); +function compilenewgol(info, env, langId) { + const compiler = new Compile(info, env, langId); compiler.originalGetDefaultExecOptions = compiler.getDefaultExecOptions; function convertNewGoL(code) { @@ -70,7 +70,7 @@ function compilenewgol(info, env) { compiler.getDefaultExecOptions = function () { const execOptions = this.originalGetDefaultExecOptions(); - const goroot = this.env.compilerProps("compiler." + this.compiler.id + ".goroot"); + const goroot = this.compilerProps("compiler." + this.compiler.id + ".goroot"); if (goroot) { execOptions.env.GOROOT = goroot; } diff --git a/lib/compilers/haskell.js b/lib/compilers/haskell.js index a4080192f..78963748e 100644 --- a/lib/compilers/haskell.js +++ b/lib/compilers/haskell.js @@ -1,7 +1,7 @@ var Compile = require('../base-compiler'); -function compileHaskell(info, env) { - var compiler = new Compile(info, env); +function compileHaskell(info, env, langId) { + var compiler = new Compile(info, env, langId); compiler.optionsForFilter = function (filters, outputFilename, userOptions) { return ['-S', '-g', '-o', this.filename(outputFilename)]; }; diff --git a/lib/compilers/ispc.js b/lib/compilers/ispc.js index 3bb0f124f..ff2b8054f 100644 --- a/lib/compilers/ispc.js +++ b/lib/compilers/ispc.js @@ -1,7 +1,7 @@ var Compile = require('../base-compiler'); -function compileISPC(info, env) { - var compiler = new Compile(info, env); +function compileISPC(info, env, langId) { + var compiler = new Compile(info, env, langId); compiler.optionsForFilter = function (filters, outputFilename, userOptions) { return ['--target=sse2-i32x4', '--emit-asm', '-g', '-o', this.filename(outputFilename)]; }; diff --git a/lib/compilers/ldc.js b/lib/compilers/ldc.js index 11de1e66c..ffee8bf95 100644 --- a/lib/compilers/ldc.js +++ b/lib/compilers/ldc.js @@ -25,8 +25,8 @@ var Compile = require('../base-compiler'), argumentParsers = require("./argument-parsers"); -function compileLdc(info, env) { - var compiler = new Compile(info, env); +function compileLdc(info, env, langId) { + var compiler = new Compile(info, env, langId); compiler.compiler.supportsIntel = true; compiler.optionsForFilter = function (filters, outputFilename, userOptions) { var options = ['-g', '-of', this.filename(outputFilename)]; diff --git a/lib/compilers/pascal.js b/lib/compilers/pascal.js index f5855f6cd..e4f0fcbc0 100644 --- a/lib/compilers/pascal.js +++ b/lib/compilers/pascal.js @@ -24,15 +24,14 @@ "use strict"; var Compile = require('../base-compiler'), - logger = require('../logger').logger, PascalDemangler = require('../pascal-support').demangler, utils = require('../utils'), fs = require("fs"), path = require("path"); -function compileFPC(info, env) { +function compileFPC(info, env, langId) { var demangler = new PascalDemangler(); - var compiler = new Compile(info, env); + var compiler = new Compile(info, env, langId); compiler.supportsOptOutput = false; var originalExecBinary = compiler.execBinary; diff --git a/lib/compilers/rust.js b/lib/compilers/rust.js index 6d2295cbe..2fc60fa2b 100644 --- a/lib/compilers/rust.js +++ b/lib/compilers/rust.js @@ -25,8 +25,8 @@ var Compile = require('../base-compiler'), _ = require('underscore-node'); -function compileRust(info, env) { - var compiler = new Compile(info, env); +function compileRust(info, env, langId) { + var compiler = new Compile(info, env, langId); compiler.compiler.supportsIntel = true; compiler.optionsForFilter = function (filters, outputFilename, userOptions) { var options = ['-C', 'debuginfo=1', '-o', this.filename(outputFilename)]; diff --git a/lib/compilers/swift.js b/lib/compilers/swift.js index 884828852..ffd39a295 100644 --- a/lib/compilers/swift.js +++ b/lib/compilers/swift.js @@ -1,8 +1,8 @@ const Compile = require('../base-compiler'), logger = require('../logger').logger; -function compileSwift(info, env) { - const compiler = new Compile(info, env); +function compileSwift(info, env, langId) { + const compiler = new Compile(info, env, langId); compiler.handlePostProcessResult = function (result, postResult) { result.asm = postResult.stdout; diff --git a/lib/languages.js b/lib/languages.js new file mode 100644 index 000000000..258a9b646 --- /dev/null +++ b/lib/languages.js @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2017, 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. + +/* id is what we internally use to identify the language. + *MUST BE* Same as the key so we can always query for it, everywhere. + Used in: properties, url redirect, lang dropdown + name is what we display to the user + Used in: UI + monaco is how we tell the monaco editor which language is this + Used in: Monaco.editor.language + extension is an array for the usual extensions of the language. + The first one declared will be used as the default, else txt + Leading point is needed + Used in: Save to file extension +*/ + +const languages = { + 'c++': { + id: 'c++', + name: 'C++', + monaco: 'cppp', + extensions: ['.cpp', '.cxx', '.h', '.hpp', '.hxx', '.c'] + }, + cppx: { + id: 'cppx', + name: 'Cppx', + monaco: 'cppp', + extensions: ['.cpp', '.cxx', '.h', '.hpp', '.hxx', '.c'] + }, + c: { + id: 'c', + name: 'C', + monaco: 'c', + extensions: ['.c', '.h'] + }, + rust: { + id: 'rust', + name: 'Rust', + monaco: 'rust', + extensions: ['.rs'] + }, + d: { + id: 'd', + name: 'D', + monaco: 'd', + extensions: ['.d'] + }, + go: { + id: 'go', + name: 'Go', + monaco: 'go', + extensions: ['.go'] + }, + ispc: { + id: 'ispc', + name: 'ispc', + monaco: 'ispc', + extensions: ['.ispc'] + }, + haskell: { + id: 'haskell', + name: 'Haskell', + monaco: 'haskell', + extensions: ['.hs', '.haskell'] + }, + swift: { + id: 'swift', + name: 'Swift', + monaco: 'swift', + extensions: ['.swift'] + }, + pascal: { + id: 'pascal', + name: 'Pascal', + monaco: 'pascal', + extensions: ['.pas'] + } +}; + +const fs = require('fs-extra'); +const _ = require('underscore-node'); +const path = require('path'); +_.each(languages, lang => { + try { + const example = fs.readFileSync(path.join('examples', lang.id, 'default' + lang.extensions[0]), 'utf8'); + lang.example = example; + } catch (error) { + lang.example = "Oops, something went wrong and we could not get the default code for this language."; + } +}); + +module.exports = { + list: languages +}; diff --git a/lib/sources/builtin.js b/lib/sources/builtin.js index d5252bc05..aaf78b3be 100644 --- a/lib/sources/builtin.js +++ b/lib/sources/builtin.js @@ -24,27 +24,31 @@ const props = require('../properties.js'), path = require('path'), - fs = require('fs'); + fs = require('fs'), + _ = require('underscore-node'); +const basePath = props.get('builtin', 'sourcePath', './examples/'); +const replacer = new RegExp('_', 'g'); +const examples = _.flatten( + fs.readdirSync(basePath) + .map(folder => { + const folerPath = path.join(basePath, folder); + return fs.readdirSync(folerPath) + .map(file => { + const filePath = path.join(folerPath, file); + const fileName = path.parse(file).name; + return {lang: folder, name: fileName.replace(replacer, ' '), path: filePath, file: fileName}; + }) + .filter(descriptor => descriptor.name !== "default") + .sort((x, y) => x.name.localeCompare(y.name)); + })); -const sourcePath = props.get('builtin', 'sourcepath', './examples/c++'); -const sourceMatch = new RegExp(props.get('builtin', 'extensionRe', '.*\\.cpp$')); -const examples = fs.readdirSync(sourcePath) - .filter(file => file.match(sourceMatch)) - .map(file => { - const nicename = file.replace(/\.cpp$/, ''); - return {urlpart: nicename, name: nicename.replace(/_/g, ' '), path: path.join(sourcePath, file)}; - }).sort((x, y) => x.name.localeCompare(y.name)); - -const byUrlpart = {}; -examples.forEach(e => byUrlpart[e.urlpart] = e.path); - -function load(filename) { - const path = byUrlpart[filename]; - if (!path) { +function load(lang, filename) { + const example = _.find(examples, example => example.lang === lang && example.file === filename); + if (!example) { return Promise.reject("No such path"); } return new Promise((resolve, reject) => { - fs.readFile(path, 'utf-8', function (err, res) { + fs.readFile(example.path, 'utf-8', (err, res) => { if (err) { reject(err); } else { @@ -55,7 +59,9 @@ function load(filename) { } function list() { - return Promise.resolve(examples.map(example => ({urlpart: example.urlpart, name: example.name}))); + return Promise.resolve(examples.map(example => { + return {file: example.file, name: example.name, lang: example.lang}; + })); } module.exports.load = load; diff --git a/static/ast-view.js b/static/ast-view.js index c14f14884..d45ffef27 100644 --- a/static/ast-view.js +++ b/static/ast-view.js @@ -28,7 +28,6 @@ define(function (require) { var FontScale = require('fontscale'); var monaco = require('monaco'); - var options = require('options'); var _ = require('underscore'); var $ = require('jquery'); diff --git a/static/compiler-service.js b/static/compiler-service.js index 6cc50dd04..2c521f616 100644 --- a/static/compiler-service.js +++ b/static/compiler-service.js @@ -38,20 +38,22 @@ define(function (require) { return JSON.stringify(n).length; } }); - this.compilersById = _.chain(options.compilers) - .map(function (compiler) { - var aliases = []; - aliases.push([compiler.id, compiler]); - if (compiler.alias) aliases.push([compiler.alias, compiler]); - return aliases; - }) - .flatten(true) - .object() - .value(); + this.compilersByLang = {}; + _.each(options.compilers, function (compiler) { + if (!this.compilersByLang[compiler.lang]) this.compilersByLang[compiler.lang] = {}; + this.compilersByLang[compiler.lang][compiler.id] = compiler; + if (compiler.alias) { + this.compilersByLang[compiler.lang][compiler.alias] = compiler; + } + }, this); } - CompilerService.prototype.getCompilerById = function (id) { - return this.compilersById[id]; + CompilerService.prototype.getCompilersForLang = function (langId) { + return this.compilersByLang[langId]; + }; + + CompilerService.prototype.findCompiler = function (langId, compilerId) { + return this.getCompilersForLang(langId)[compilerId]; }; CompilerService.prototype.submit = function (request) { diff --git a/static/compiler.js b/static/compiler.js index 2ab24dc30..a4fe29c58 100644 --- a/static/compiler.js +++ b/static/compiler.js @@ -44,7 +44,7 @@ define(function (require) { var OpcodeCache = new LruCache({ max: 64 * 1024, - length: function (n) { + length:function (n) { return JSON.stringify(n).length; } }); @@ -72,11 +72,14 @@ define(function (require) { this.domRoot.html($('#compiler').html()); this.id = state.id || hub.nextCompilerId(); this.sourceEditorId = state.source || 1; - this.compiler = this.compilerService.getCompilerById(state.compiler) || - this.compilerService.getCompilerById(options.defaultCompiler); + this.currentLangId = state.lang || "c++"; + this.originalCompilerId = state.compiler; + this.compiler = this.compilerService.findCompiler(this.currentLangId, this.originalCompilerId) || + this.compilerService.findCompiler(this.currentLangId, options.defaultCompiler[this.currentLangId]); + this.selectedCompilerByLang = {}; this.deferCompiles = hub.deferred; this.needsCompile = false; - this.options = state.options || options.compileOptions; + this.options = state.options || options.compileOptions[this.currentLangId]; this.filters = new Toggles(this.domRoot.find('.filters'), patchOldFilters(state.filters)); this.source = ''; this.assembly = []; @@ -96,16 +99,21 @@ define(function (require) { this.gccDumpButton = this.domRoot.find('.btn.view-gccdump'); this.cfgButton = this.domRoot.find('.btn.view-cfg'); this.libsButton = this.domRoot.find('.btn.show-libs'); - - this.availableLibs = $.extend(true, {}, options.libs); - this.compileTimeLabel = this.domRoot.find('.compile-time'); this.compileClearCache = this.domRoot.find('.clear-cache'); + this.availableLibs = {}; + this.updateAvailableLibs = function() { + if (!this.availableLibs[this.currentLangId]) { + this.availableLibs[this.currentLangId] = $.extend(true, {}, options.libs[this.currentLangId]); + } + }; + this.updateAvailableLibs(); _.each(state.libs, _.bind(function (lib) { - if (this.availableLibs[lib.name] && this.availableLibs[lib.name].versions && - this.availableLibs[lib.name].versions[lib.ver]) { - this.availableLibs[lib.name].versions[lib.ver].used = true; + if (this.availableLibs[this.currentLangId][lib.name] && + this.availableLibs[this.currentLangId][lib.name].versions && + this.availableLibs[this.currentLangId][lib.name].versions[lib.ver]) { + this.availableLibs[this.currentLangId][lib.name].versions[lib.ver].used = true; } }, this)); @@ -116,16 +124,18 @@ define(function (require) { valueField: 'id', labelField: 'name', searchField: ['name'], - options: options.compilers, + options: _.map(this.getCurrentLangCompilers(), _.identity), items: this.compiler ? [this.compiler.id] : [] }).on('change', _.bind(function (e) { var val = $(e.target).val(); - ga('send', { - hitType: 'event', - eventCategory: 'SelectCompiler', - eventAction: val - }); - this.onCompilerChange(val); + if (val) { + ga('send', { + hitType: 'event', + eventCategory: 'SelectCompiler', + eventAction: val + }); + this.onCompilerChange(val); + } }, this)); var optionsChange = _.debounce(_.bind(function (e) { this.onOptionsChange($(e.target).val()); @@ -260,6 +270,7 @@ define(function (require) { this.eventHub.emit('filtersChange', this.id, this.getEffectiveFilters()); } }, this); + this.eventHub.on('languageChange', this.onLanguageChange, this); this.eventHub.emit('requestSettings'); this.sendCompiler(); this.updateCompilerName(); @@ -345,110 +356,7 @@ define(function (require) { insertPoint.addChild(createCfgView); }, this)); - - var updateLibsUsed = _.bind(function () { - var libsCount = Object.keys(this.availableLibs).length; - if (libsCount === 0) { - return $('<p></p>') - .text('No libs configured for this language yet. ') - .append($('<a></a>') - .attr('target', '_blank') - .attr('rel', 'noopener noreferrer') - .attr('href', 'https://github.com/mattgodbolt/compiler-explorer/issues/new') - .text('You can suggest us one at any time ') - .append($('<sup></sup>') - .addClass('glyphicon glyphicon-new-window') - .width('16px') - .height('16px') - .attr('title', 'Opens in a new window') - ) - ); - } - var columnCount = Math.ceil(libsCount / 5); - var currentLibIndex = -1; - - var libLists = []; - for (var i = 0; i < columnCount; i++) { - libLists.push($('<ul></ul>').addClass('lib-list')); - } - - // Utility function so we can iterate indefinetly over our lists - var getNextList = function () { - currentLibIndex = (currentLibIndex + 1) % columnCount; - return libLists[currentLibIndex]; - }; - - var onChecked = _.bind(function (e) { - var elem = $(e.target); - // Uncheck every lib checkbox with the same name if we're checking the target - if (elem.prop('checked')) { - var others = $.find('input[name=\'' + elem.prop('name') + '\']'); - _.each(others, function (other) { - $(other).prop('checked', false); - }); - // Recheck the targeted one - elem.prop('checked', true); - } - // And now do the same with the availableLibs object - _.each(this.availableLibs[elem.prop('data-lib')].versions, function (version) { - version.used = false; - }); - this.availableLibs[elem.prop('data-lib')].versions[elem.prop('data-version')].used = elem.prop('checked'); - this.saveState(); - this.compile(); - }, this); - - _.each(this.availableLibs, function (lib, libKey) { - var libsList = getNextList(); - var libCat = $('<li></li>') - .append($('<span></span>') - .text(lib.name) - .addClass('lib-header') - ) - .addClass('lib-item'); - - var libGroup = $('<div></div>'); - - if (libsList.children().length > 0) - libsList.append($('<hr>').addClass('lib-separator')); - - _.each(lib.versions, function (version, vKey) { - libGroup.append($('<div></div>') - .append($('<input type="checkbox">') - .addClass('lib-checkbox') - .prop('data-lib', libKey) - .prop('data-version', vKey) - .prop('checked', version.used) - .prop('name', libKey) - .on('change', onChecked) - ).append($('<label></label>') - .addClass('lib-label') - .text(lib.name + ' ' + version.version) - .on('click', function () { - $(this).parent().find('.lib-checkbox').trigger('click'); - }) - ) - ); - }); - libGroup.appendTo(libCat); - libCat.appendTo(libsList); - }); - return $('<div></div>').addClass('libs-container').append(libLists); - }, this); - - this.libsButton.popover({ - container: 'body', - content: updateLibsUsed(), - html: true, - placement: 'bottom', - trigger: 'manual' - }).click(_.bind(function () { - this.libsButton.popover('show'); - }, this)).on('inserted.bs.popover', function (e) { - $(e.target).content = updateLibsUsed().html(); - }).on('show.bs.popover', function () { - $(this).data('bs.popover').tip().css('max-width', '100%').css('width', 'auto'); - }); + this.updateLibsDropdown(); this.compileClearCache.on('click', _.bind(function () { this.compilerService.cache.reset(); @@ -541,7 +449,8 @@ define(function (require) { var request = { source: expanded || '', compiler: this.compiler ? this.compiler.id : '', - options: options + options: options, + lang: this.currentLangId }; if (!this.compiler) { this.onCompileResponse(request, errorResult('<Please select a compiler>'), false); @@ -861,7 +770,7 @@ define(function (require) { }; Compiler.prototype.onCompilerChange = function (value) { - this.compiler = this.compilerService.getCompilerById(value); + this.compiler = this.compilerService.findCompiler(this.currentLangId, value); this.saveState(); this.compile(); this.updateButtons(); @@ -906,7 +815,8 @@ define(function (require) { // NB must *not* be effective filters filters: this.filters.get(), wantOptInfo: this.wantOptInfo, - libs: libs + libs: libs, + lang: this.currentLangId }; this.fontScale.addState(state); return state; @@ -1128,6 +1038,150 @@ define(function (require) { ); }; + Compiler.prototype.updateLibsDropdown = function () { + this.updateAvailableLibs(); + this.libsButton.popover({ + container: 'body', + content: _.bind(function () { + var libsCount = Object.keys(this.availableLibs[this.currentLangId]).length; + if (libsCount === 0) { + return $('<p></p>') + .text('No libs configured for ' + options.languages[this.currentLangId].name + ' yet. ') + .append($('<a></a>') + .attr('target', '_blank') + .attr('rel', 'noopener noreferrer') + .attr('href', 'https://github.com/mattgodbolt/compiler-explorer/issues/new') + .text('You can suggest us one at any time ') + .append($('<sup></sup>') + .addClass('glyphicon glyphicon-new-window') + .width('16px') + .height('16px') + .attr('title', 'Opens in a new window') + ) + ); + } + var columnCount = Math.ceil(libsCount / 5); + var currentLibIndex = -1; + + var libLists = []; + for (var i = 0; i < columnCount; i++) { + libLists.push($('<ul></ul>').addClass('lib-list')); + } + + // Utility function so we can iterate indefinetly over our lists + var getNextList = function () { + currentLibIndex = (currentLibIndex + 1) % columnCount; + return libLists[currentLibIndex]; + }; + + var onChecked = _.bind(function (e) { + var elem = $(e.target); + // Uncheck every lib checkbox with the same name if we're checking the target + if (elem.prop('checked')) { + var others = $.find('input[name=\'' + elem.prop('name') + '\']'); + _.each(others, function (other) { + $(other).prop('checked', false); + }); + // Recheck the targeted one + elem.prop('checked', true); + } + // And now do the same with the availableLibs object + _.each(this.availableLibs[this.currentLangId][elem.prop('data-lib')].versions, function (version) { + version.used = false; + }); + this.availableLibs[this.currentLangId][elem.prop('data-lib')].versions[elem.prop('data-version')].used = elem.prop('checked'); + this.saveState(); + this.compile(); + }, this); + + _.each(this.availableLibs[this.currentLangId], function (lib, libKey) { + var libsList = getNextList(); + var libCat = $('<li></li>') + .append($('<span></span>') + .text(lib.name) + .addClass('lib-header') + ) + .addClass('lib-item'); + + var libGroup = $('<div></div>'); + + if (libsList.children().length > 0) + libsList.append($('<hr>').addClass('lib-separator')); + + _.each(lib.versions, function (version, vKey) { + libGroup.append($('<div></div>') + .append($('<input type="checkbox">') + .addClass('lib-checkbox') + .prop('data-lib', libKey) + .prop('data-version', vKey) + .prop('checked', version.used) + .prop('name', libKey) + .on('change', onChecked) + ).append($('<label></label>') + .addClass('lib-label') + .text(lib.name + ' ' + version.version) + .on('click', function () { + $(this).parent().find('.lib-checkbox').trigger('click'); + }) + ) + ); + }); + libGroup.appendTo(libCat); + libCat.appendTo(libsList); + }); + return $('<div></div>').addClass('libs-container').append(libLists); + }, this), + html: true, + placement: 'bottom', + trigger: 'manual' + }).click(_.bind(function () { + this.libsButton.popover('show'); + }, this)).on('show.bs.popover', function () { + $(this).data('bs.popover').tip().css('max-width', '100%').css('width', 'auto'); + }); + }; + + Compiler.prototype.onLanguageChange = function (editorId, newLangId) { + if (this.sourceEditorId === editorId) { + var oldLangId = this.currentLangId; + this.currentLangId = newLangId; + // Store the current selected compiler to come back to it later in the same session (Not state sotred!) + this.selectedCompilerByLang[oldLangId] = this.compiler ? this.compiler.id : options.defaultCompiler[oldLangId]; + this.updateCompilersSelector(); + this.updateLibsDropdown(); + } + }; + + Compiler.prototype.getCurrentLangCompilers = function () { + return this.compilerService.getCompilersForLang(this.currentLangId); + }; + + Compiler.prototype.updateCompilersSelector = function () { + var selector = this.domRoot.find('.compiler-picker')[0].selectize; + selector.clearOptions(); + selector.load(_.bind(function (callback) { + callback(_.map(this.getCurrentLangCompilers(), _.identity)); + }, this)); + var defaultOrFirst = _.bind(function defaultOrFirst () { + // If the default is a valid compiler, return it + var defaultCompiler = options.defaultCompiler[this.currentLangId]; + if (defaultCompiler && options.compilers[defaultCompiler]) return defaultCompiler; + // Else try to find the first one for this language + var value = _.find(options.compilers, _.bind(function (compiler) { + return compiler.lang === this.currentLangId; + }, this)); + + // Return the first, or an empty string if none found (Should prob report this one...) + return value && value.id ? value.id : ""; + }, this); + + selector.setValue([this.selectedCompilerByLang[this.currentLangId] || defaultOrFirst()]); + }; + + Compiler.prototype.findCompiler = function (langId, compilerId) { + return this.compilerService.findCompiler(langId, compilerId); + }; + return { Compiler: Compiler }; diff --git a/static/conformance-view.js b/static/conformance-view.js index b50ab0a20..39a51603e 100644 --- a/static/conformance-view.js +++ b/static/conformance-view.js @@ -42,9 +42,9 @@ define(function (require) { this.addCompilerButton = this.domRoot.find('.add-compiler'); this.selectorTemplate = $('#compiler-selector .compiler-row'); this.editorId = state.editorid; - this.source = state.source || ""; this.nextSelectorId = 0; this.maxCompilations = options.cvCompilerCountMax || 6; + this.langId = state.langId || 'c++'; this.status = { allowCompile: false, @@ -67,6 +67,7 @@ define(function (require) { this.eventHub.on('editorChange', this.onEditorChange, this); this.eventHub.on('editorClose', this.onEditorClose, this); + this.eventHub.on('languageChange', this.onLanguageChange, this); this.addCompilerButton.on('click', _.bind(function () { this.addCompilerSelector(); @@ -129,6 +130,10 @@ define(function (require) { this.selectorList.append(newEntry); var status = newEntry.find('.status').attr("data-cv", config.cv); + var langId = this.langId; + var isVisible = function (compiler) { + return compiler.lang == langId; + }; newEntry.find('.compiler-picker') .attr("data-cv", config.cv) @@ -137,7 +142,7 @@ define(function (require) { valueField: 'id', labelField: 'name', searchField: ['name'], - options: options.compilers, + options: _.filter(options.compilers, isVisible), items: config.compilerId ? [config.compilerId] : [] }) .on('change', _.bind(function () { @@ -149,6 +154,7 @@ define(function (require) { }, this)); this.handleStatusIcon(status, {code: 0, text: ""}); this.handleToolbarUI(); + this.saveState(); }; Conformance.prototype.removeCompilerSelector = function (cv) { @@ -162,8 +168,9 @@ define(function (require) { this.saveState(); }; - Conformance.prototype.onEditorChange = function (editorId, newSource) { + Conformance.prototype.onEditorChange = function (editorId, newSource, langId) { if (editorId == this.editorId) { + this.langId = langId; this.source = newSource; this.compileAll(); } @@ -190,6 +197,7 @@ define(function (require) { }; Conformance.prototype.compileAll = function () { + if (!this.source) return; // Hide previous status icons this.selectorList.find('.status').css("visibility", "hidden"); this.compilerService.expand(this.source).then(_.bind(function (expanded) { @@ -251,11 +259,10 @@ define(function (require) { Conformance.prototype.currentState = function () { var state = { editorid: this.editorId, - source: this.source, + langId: this.langId, compilers: [] }; _.each(this.selectorList.children(), _.bind(function (child) { - var status = $(child).find('.status'); state.compilers.push({ // Code we have cv: $(child).attr("data-cv"), @@ -276,6 +283,18 @@ define(function (require) { this.selectorList.css("height", this.domRoot.height() - this.domRoot.find('.top-bar').outerHeight(true)); }; + Conformance.prototype.onLanguageChange = function (editorId, newLangId) { + if (editorId === this.editorId) { + this.langId = newLangId; + // Sorry for this future me. You promised to come back and enchance this after the unification merge + // Hopefuly it's been soon enough for noone to notice :) + this.selectorList.children().remove(); + this.nextSelectorId = 0; + this.handleToolbarUI(); + this.saveState(); + } + }; + return { Conformance: Conformance }; diff --git a/static/editor.js b/static/editor.js index 843defb6f..366f993be 100644 --- a/static/editor.js +++ b/static/editor.js @@ -41,10 +41,13 @@ define(function (require) { require('./haskell-mode'); require('./swift-mode'); require('./pascal-mode'); + require('selectize'); var loadSave = new loadSaveLib.LoadSave(); - function Editor(hub, state, container, lang, defaultSrc) { + var languages = options.languages; + + function Editor(hub, state, container) { this.id = state.id || hub.nextEditorId(); this.container = container; this.domRoot = container.getElement(); @@ -57,63 +60,28 @@ define(function (require) { this.asmByCompiler = {}; this.busyCompilers = {}; this.colours = []; - this.lastCompilerIDResponse = -1; this.decorations = {}; this.prevDecorations = []; this.fadeTimeoutId = -1; - var cmMode; - // The first one is used as the default file extension when saving to local file. - // All of them are used as the contents of the accept attribute of the file input - var extensions = []; - switch (lang.toLowerCase()) { - default: - cmMode = "cppp"; - extensions = ['.cpp', '.cxx', '.h', '.hpp', '.hxx']; - break; - case "c": - // C Plus Plus Plus. C++ without invalid keywords! - cmMode = "cppp"; - extensions = ['.cpp', '.cxx', '.h', '.hpp', '.hxx']; - break; - case "rust": - cmMode = "rust"; - extensions = ['.rs']; - break; - case "d": - cmMode = "d"; - extensions = ['.d']; - break; - case "go": - cmMode = "go"; - extensions = ['.go']; - break; - case "ispc": - cmMode = "ispc"; - extensions = ['.ispc']; - break; - case "haskell": - cmMode = "haskell"; - extensions = ['.hs']; - break; - case "swift": - cmMode = "swift"; - extensions = ['.swift']; - break; - case "pascal": - cmMode = "pascal"; - extensions = ['.pas']; - break; + this.editorSourceByLang = {}; + + this.languageBtn = this.domRoot.find('.change-language'); + this.needsLanguageUpdate = !(state.lang && languages[state.lang]); + this.currentLanguage = languages["c++"]; + if (state.lang && languages[state.lang]) { + this.currentLanguage = languages[state.lang]; + } else if (hub.lastOpenedLangId && languages[hub.lastOpenedLangId]) { + this.currentLanguage = languages[hub.lastOpenedLangId]; } var root = this.domRoot.find(".monaco-placeholder"); var legacyReadOnly = state.options && !!state.options.readOnly; this.editor = monaco.editor.create(root[0], { - value: state.source || defaultSrc || "", scrollBeyondLastLine: false, - language: cmMode, + language: this.currentLanguage.monaco, fontFamily: 'Fira Mono', readOnly: !!options.readOnly || legacyReadOnly, glyphMargin: !options.embedded, @@ -125,9 +93,17 @@ define(function (require) { folding: true, lineNumbersMinChars: options.embedded ? 1 : 3, emptySelectionClipboard: true, - autoIndent: true, + autoIndent: true }); + if (state.source) { + this.setSource(state.source); + } else { + this.updateEditorCode(); + } + + this.initLoadSaver(); + var startFolded = /^[/*#;]+\s*setup.*/; if (state.source && state.source.match(startFolded)) { var foldAction = this.editor.getAction('editor.fold'); @@ -175,6 +151,18 @@ define(function (require) { }, this) }); + this.editor.addAction({ + id: 'viewasm', + label: 'Scroll to assembly', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10], + keybindingContext: null, + contextMenuGroupId: 'navigation', + contextMenuOrder: 1.5, + run: function (ed) { + tryCompilerLinkLine(ed.getPosition().lineNumber, true); + } + }); + var tryCompilerLinkLine = _.bind(function (thisLineNumber, reveal) { _.each(this.asmByCompiler, _.bind(function (asms, compilerId) { var targetLines = []; @@ -194,18 +182,6 @@ define(function (require) { }, this)); }, this); - this.editor.addAction({ - id: 'viewasm', - label: 'Scroll to assembly', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10], - keybindingContext: null, - contextMenuGroupId: 'navigation', - contextMenuOrder: 1.5, - run: function (ed) { - tryCompilerLinkLine(ed.getPosition().lineNumber, true); - } - }); - this.editor.onMouseLeave(_.bind(function () { this.fadeTimeoutId = setTimeout(_.bind(function () { clearCompilerLinkedLines(); @@ -217,10 +193,7 @@ define(function (require) { if (e !== null && e.target !== null && this.settings.hoverShowSource && e.target.position !== null) { tryCompilerLinkLine(e.target.position.lineNumber, false); } - }, - this), - 250 - ); + }, this), 250); this.editor.onMouseMove(_.bind(function (e) { this.mouseMoveThrottledFunction(e); @@ -231,9 +204,29 @@ define(function (require) { } }, this)); + this.updateEditorLayout = _.bind(function () { + var topBarHeight = this.domRoot.find(".top-bar").outerHeight(true) || 0; + this.editor.layout({width: this.domRoot.width(), height: this.domRoot.height() - topBarHeight}); + }, this); + this.fontScale = new FontScale(this.domRoot, state, this.editor); this.fontScale.on('change', _.bind(this.updateState, this)); + this.languageBtn.selectize({ + sortField: 'name', + valueField: 'id', + labelField: 'name', + searchField: ['name'], + options: _.map(languages, _.identity), + items: [this.currentLanguage.id] + }).on('change', _.bind(function (e) { + this.onLanguageChange($(e.target).val()); + }, this)); + + this.changeLanguage = function (newLang) { + this.languageBtn[0].selectize.setValue(newLang); + }; + // We suppress posting changes until the user has stopped typing by: // * Using _.debounce() to run emitChange on any key event or change // only after a delay. @@ -252,21 +245,8 @@ define(function (require) { // this.debouncedEmitChange(); // }, this)); - var layout = _.bind(function () { - var topBarHeight = this.domRoot.find(".top-bar").outerHeight(true) || 0; - this.editor.layout({width: this.domRoot.width(), height: this.domRoot.height() - topBarHeight}); - }, this); - - this.domRoot.find('.load-save').click(_.bind(function () { - loadSave.run(_.bind(function (text) { - this.editor.setValue(text); - this.updateState(); - this.maybeEmitChange(); - }, this), this.getSource(), extensions); - }, this)); - - container.on('resize', layout); - container.on('shown', layout); + container.on('resize', this.updateEditorLayout); + container.on('shown', this.updateEditorLayout); container.on('open', _.bind(function () { this.eventHub.emit('editorOpen', this.id); }, this)); @@ -275,7 +255,6 @@ define(function (require) { this.eventHub.emit('editorClose', this.id); this.editor.dispose(); }, this)); - container.setTitle(lang + " source #" + this.id); this.container.layoutManager.on('initialised', function () { // Once initialized, let everyone know what text we have. this.maybeEmitChange(); @@ -290,7 +269,7 @@ define(function (require) { this.eventHub.on('settingsChange', this.onSettingsChange, this); this.eventHub.on('conformanceViewOpen', this.onConformanceViewOpen, this); this.eventHub.on('conformanceViewClose', this.onConformanceViewClose, this); - this.eventHub.on('resize', layout, this); + this.eventHub.on('resize', this.updateEditorLayout, this); this.eventHub.emit('requestSettings'); // NB a new compilerConfig needs to be created every time; else the state is shared @@ -324,21 +303,24 @@ define(function (require) { this.container.layoutManager.root.contentItems[0]; insertPoint.addChild(conformanceConfig); }, this)); + this.container.setTitle(this.currentLanguage.name + " source #" + this.id); + this.eventHub.on('initialised', this.maybeEmitChange, this); this.updateState(); } Editor.prototype.maybeEmitChange = function (force) { var source = this.getSource(); - if (!force && source == this.lastChangeEmitted) return; + if (!force && source === this.lastChangeEmitted) return; this.lastChangeEmitted = source; - this.eventHub.emit('editorChange', this.id, this.lastChangeEmitted); + this.eventHub.emit('editorChange', this.id, this.lastChangeEmitted, this.currentLanguage.id); }; Editor.prototype.updateState = function () { var state = { id: this.id, - source: this.getSource() + source: this.getSource(), + lang: this.currentLanguage.id }; this.fontScale.addState(state); this.container.setState(state); @@ -420,6 +402,20 @@ define(function (require) { Editor.prototype.onCompilerOpen = function (compilerId, editorId) { if (editorId === this.id) { // On any compiler open, rebroadcast our state in case they need to know it. + if (this.needsLanguageUpdate) { + var glCompiler =_.find(this.container.layoutManager.root.getComponentsByName("compiler"), function (compiler) { + return compiler.id === compilerId; + }); + if (glCompiler) { + var selected = _.find(options.compilers, function (compiler) { + return compiler.id === glCompiler.originalCompilerId; + }); + if (selected) { + this.needsLanguageUpdate = false; + this.changeLanguage(selected.lang); + } + } + } this.maybeEmitChange(true); this.ourCompilers[compilerId] = true; } @@ -470,7 +466,6 @@ define(function (require) { }, this); this.updateDecorations(); this.asmByCompiler[compilerId] = result.asm; - this.lastCompilerIDResponse = compilerId; this.numberUsedLines(); }; @@ -518,6 +513,40 @@ define(function (require) { } }; + Editor.prototype.initLoadSaver = function () { + this.domRoot.find('.load-save') + .off('click') + .click(_.bind(function () { + loadSave.run(_.bind(function (text) { + this.setSource(text); + this.updateState(); + this.maybeEmitChange(); + }, this), this.getSource(), this.currentLanguage); + }, this)); + }; + + Editor.prototype.onLanguageChange = function (newLangId) { + if (newLangId !== this.currentLanguage.id && languages[newLangId]) { + var oldLangId = this.currentLanguage.id; + // Save the current source, so we can come back to it later + this.editorSourceByLang[oldLangId] = this.getSource(); + this.currentLanguage = languages[newLangId]; + this.initLoadSaver(); + monaco.editor.setModelLanguage(this.editor.getModel(), this.currentLanguage.monaco); + // And now set the editor value to either the saved one or the default to the new lang + this.updateEditorCode(); + this.container.setTitle(this.currentLanguage.name + " source #" + this.id); + this.updateState(); + // Broadcast the change to other panels + this.eventHub.emit("languageChange", this.id, newLangId); + } + }; + + // Called every time we change language, so we get the relevant code + Editor.prototype.updateEditorCode = function () { + this.setSource(this.editorSourceByLang[this.currentLanguage.id] || languages[this.currentLanguage.id].example); + }; + return { Editor: Editor }; diff --git a/static/explorer.css b/static/explorer.css index 194b4bccd..6b65ab21c 100644 --- a/static/explorer.css +++ b/static/explorer.css @@ -263,3 +263,7 @@ pre.content { position: absolute; right: 0; } + +.change-language { + min-width: 100px !important; +} diff --git a/static/hub.js b/static/hub.js index cf31d03c8..1fc27dd55 100644 --- a/static/hub.js +++ b/static/hub.js @@ -27,7 +27,6 @@ define(function (require) { 'use strict'; var _ = require('underscore'); - var options = require('options'); var editor = require('editor'); var compiler = require('compiler'); var output = require('output'); @@ -60,14 +59,14 @@ define(function (require) { throw 'Ran out of ids!?'; }; - function Hub(layout, defaultSrc) { + function Hub(layout) { this.layout = layout; - this.defaultSrc = defaultSrc; this.editorIds = new Ids(); this.compilerIds = new Ids(); this.compilerService = new CompilerService(); this.deferred = true; this.deferredEmissions = []; + this.lastOpenedLangId = null; // FIXME // We can't avoid this self as _ is undefined at this point @@ -122,6 +121,9 @@ define(function (require) { layout.eventHub.on('compilerClose', function (id) { this.compilerIds.remove(id); }, this); + layout.eventHub.on('languageChange', function (editorId, langId) { + this.lastOpenedLangId = langId; + }, this); layout.init(); this.undefer(); layout.eventHub.emit('initialised'); @@ -148,7 +150,7 @@ define(function (require) { // NB there doesn't seem to be a better way to do this than reach into the config and rely on the fact nothing // has used it yet. container.parent.config.isClosable = true; - return new editor.Editor(this, state, container, options.language, this.defaultSrc); + return new editor.Editor(this, state, container); }; Hub.prototype.compilerFactory = function (container, state) { diff --git a/static/loadSave.js b/static/loadSave.js index 884e87088..c1d16a455 100644 --- a/static/loadSave.js +++ b/static/loadSave.js @@ -52,23 +52,31 @@ define(function (require) { this.modal.find('.save-button').click(_.bind(this.onSaveToBrowserStorage, this)); this.modal.find('.save-file').click(_.bind(this.onSaveToFile, this)); - this.populateBuiltins(); + this.fetchBuiltins(); } - LoadSave.prototype.populateBuiltins = function () { + LoadSave.prototype.fetchBuiltins = function () { $.getJSON('source/builtin/list', _.bind(function (list) { - this.populate( - this.modal.find('.examples'), - _.map(list, _.bind(function (elem) { - return { - name: elem.name, load: _.bind(function () { - this.doLoad(elem.urlpart); - }, this) - }; - }, this))); + this.savedBuiltins = list; }, this)); }; + LoadSave.prototype.populateBuiltins = function () { + var isVisible = _.bind(function (entry) { + return this.currentLanguage && this.currentLanguage.id === entry.lang; + }, this); + this.populate(this.modal.find('.examples'), + _.map(_.filter(this.savedBuiltins, isVisible), _.bind(function (elem) { + return { + name: elem.name, + load: _.bind(function () { + this.doLoad(elem); + }, this) + }; + }, this)) + ); + }; + LoadSave.prototype.populateLocalStorage = function () { this.populate( this.modal.find('.local-storage'), @@ -108,13 +116,15 @@ define(function (require) { this.modal.modal('hide'); }; - LoadSave.prototype.run = function (onLoad, editorText, extensions) { + LoadSave.prototype.run = function (onLoad, editorText, currentLanguage) { this.populateLocalStorage(); this.onLoad = onLoad; this.editorText = editorText; // In case we don't send anything... - this.extension = extensions[0] || '.txt'; - this.modal.find('.local-file').attr('accept', _.map(extensions, function (extension) { + this.currentLanguage = currentLanguage; + this.populateBuiltins(); + this.extension = currentLanguage.extensions[0] || '.txt'; + this.modal.find('.local-file').attr('accept', _.map(currentLanguage.extensions, function (extension) { return extension + ', '; }, this)); this.modal.modal(); @@ -126,6 +136,7 @@ define(function (require) { this.alert.alert("Save name", "Invalid save name"); return; } + name += " (" + this.currentLanguage.name + ")"; var done = _.bind(function () { setLocalFile(name, this.editorText); }, this); @@ -153,11 +164,12 @@ define(function (require) { } }; - LoadSave.prototype.doLoad = function (urlpart) { + LoadSave.prototype.doLoad = function (element) { // TODO: handle errors. consider promises... - $.getJSON('source/builtin/load/' + urlpart, _.bind(function (response) { - this.onLoad(response.file); - }, this)); + $.getJSON('source/builtin/load/' + element.lang + '/' + element.file, + _.bind(function (response) { + this.onLoad(response.file); + }, this)); this.modal.modal('hide'); }; diff --git a/static/main.js b/static/main.js index 948dabfed..f424162f8 100644 --- a/static/main.js +++ b/static/main.js @@ -99,12 +99,11 @@ define(function (require) { var options = require('options'); - var defaultSrc = $('.template .lang').text().trim(); var defaultConfig = { settings: {showPopoutIcon: false}, content: [{type: 'row', content: [Components.getEditor(1), Components.getCompiler(1)]}] }; - + $(window).bind('hashchange', function () { // punt on hash events and just reload the page if there's a hash if (window.location.hash.substr(1)) @@ -141,11 +140,11 @@ define(function (require) { var hub; try { layout = new GoldenLayout(config, root); - hub = new Hub(layout, defaultSrc); + hub = new Hub(layout); } catch (e) { Raven.captureException(e); layout = new GoldenLayout(defaultConfig, root); - hub = new Hub(layout, defaultSrc); + hub = new Hub(layout); } layout.on('stateChanged', function () { var config = layout.toConfig(); diff --git a/static/themes/dark/theme-dark.css b/static/themes/dark/theme-dark.css index 2ffe1c248..6a7844866 100644 --- a/static/themes/dark/theme-dark.css +++ b/static/themes/dark/theme-dark.css @@ -94,6 +94,11 @@ body { background-color: #666 !important; } +.selectize-control.single .selectize-input.disabled { + color: #eee !important; + background: #000 !important; +} + .dropdown-menu { color: #eee !important; background-color: #303030 !important; diff --git a/test/builtin-tests.js b/test/builtin-tests.js new file mode 100644 index 000000000..4201abf8b --- /dev/null +++ b/test/builtin-tests.js @@ -0,0 +1,68 @@ +// Copyright (c) 2017, Matt Godbolt & Rubén Rincón +// 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. + +const should = require('chai').should(), + builtin = require('../lib/sources/builtin'), + languages = require('../lib/languages').list, + _ = require('underscore-node'); + +describe('Builtin sources', () => { + it('Does not include default code', () => { + builtin.list((_, list) => { + list.forEach(example => { + should.not.equal(example.name, "default"); + }); + }); + }); + it('Has a valid language listed', () => { + builtin.list((_, list) => { + list.forEach(example => { + should.not.equal(languages[example.lang], undefined, `Builtin ${example.name} has unrecognised language ${example.lang}`); + }); + }); + }); + it('Has at least one example for each language', () => { + builtin.list((placeholder, list) => { + _.each(languages, lang => { + should.not.equal(_.find(list, elem => elem.lang === lang.id), undefined, `Language ${lang.name} does not have any builtins`); + }); + }); + }); + it('Reports a string error if no example found', () => { + builtin.load('BADLANG', 'BADFILE', (error, result) => { + should.equal(result, undefined, 'A result should not be returned for bad requests'); + error.should.be.a("string"); + }); + }); + it('Reports something for every defined example', () => { + builtin.list((placeholder, examples) => { + examples.forEach(example => { + builtin.load(example.lang, example.file, (error, result) => { + should.not.exist(error, `Can't read ${example.name} for ${example.lang} in ${example.file}`); + result.file.should.be.a('string'); + }); + }); + }); + }); +});
\ No newline at end of file diff --git a/test/lang-tests.js b/test/lang-tests.js new file mode 100644 index 000000000..1393d0fc0 --- /dev/null +++ b/test/lang-tests.js @@ -0,0 +1,47 @@ +// Copyright (c) 2017, Matt Godbolt & Rubén Rincón +// 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 should = require('chai').should(), + languages = require('../lib/languages').list, + fs = require('fs-extra'), + path = require('path'); + + +describe('Language definitions tests', () => { + it('Has id equal to object key', () => { + Object.keys(languages).forEach(languageKey => should.equal(languages[languageKey].id, languageKey)); + }); + it ('Has extensions with leading dots', () => { + Object.keys(languages).forEach(languageKey => should.equal(languages[languageKey].extensions[0][0], '.')); + }); + it('Has examples & are initialized', () => { + Object.keys(languages).forEach(languageKey => { + const lang = languages[languageKey]; + fs.stat(path.join('examples', lang.id, 'default' + lang.extensions[0]), (err, fd) => { + should.equal(err, null); + should.equal(fd, lang.example); + }); + }); + }); +});
\ No newline at end of file diff --git a/views/head.pug b/views/head.pug index 2acb3f76f..766cefba7 100644 --- a/views/head.pug +++ b/views/head.pug @@ -1,24 +1,7 @@ -title Compiler Explorer - #{language} +title Compiler Explorer meta(charset="utf-8") meta(http-equiv="X-UA-Compatible" content="IE=edge") -case language - when "Rust" - meta(name="description" content="Compiler Explorer for Rust is an interactive online compiler which shows the assembly output of compiled Rust code.") - when "Go" - meta(name="description" content="Compiler Explorer for Go is an interactive online compiler which shows the assembly output of compiled Go code.") - when "D" - meta(name="description" content="Compiler Explorer for D is an interactive online compiler which shows the assembly output of compiled D code.") - when "Haskell" - meta(name="description" content="Compiler Explorer for Haskell is an interactive online compiler which shows the assembly output of compiled Haskell code.") - when "ispc" - meta(name="description" content="Compiler Explorer for the C variant ispc is an interactive online compiler which shows the assembly output of compiled ispc code.") - when "swift" - meta(name="description" content="Compiler Explorer for Swift is an interactive online compiler which shows the assembly output of compiled Swift code.") - when "Pascal" - meta(name="description" content="Compiler Explorer for Pascal is an interactive online compiler which shows the assembly output of compiled Pascal code.") - when "C++" - default - meta(name="description" content="Compiler Explorer is an interactive online compiler which shows the assembly output of compiled C/C++/Rust/Go/D/Haskell/Swift/Pascal code.") +meta(name="description" content="Compiler Explorer is an interactive online compiler which shows the assembly output of compiled C, C++, Rust, Go, D, Haskell, Swift & Pascal code.") meta(name="author" content="Matt Godbolt") meta(name="google" content="nositelinkssearchbox") meta(name="google" content="notranslate") diff --git a/views/index.pug b/views/index.pug index af386444b..fa1260751 100644 --- a/views/index.pug +++ b/views/index.pug @@ -15,17 +15,6 @@ html(lang="en") a.navbar-brand(href="#" title="Compiler Explorer") Compiler Explorer li.navbar-collapse.collapse#navbar-collapse ul.nav.navbar-nav.navbar-left - if languages.length - li.dropdown#lang-dropdown - a(href="#" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false") - span= language - span.caret - ul.dropdown-menu.dropdown-brand - each language in languages - li: a(href=language.url)= language.language - else - li#lang-dropdown - a(href="#"): span= language li: a#add-editor(href="#" title="Click or drag to desired destination") Editor li: a#add-diff(href="#" title="Click or drag to desired destination") Diff View li.dropdown @@ -95,18 +84,5 @@ html(lang="en") src=root + "assets/clippy.svg" width="13px" alt="Copy to clipboard") .lang - case language - when "C++" - if environment.indexOf("cppx") >= 0 - include example.cppx - else - include example.cpp - when "Rust": include example.rs - when "Go": include example.go - when "D": include example.d - when "ispc": include example.ispc - when "Haskell": include example.hs - when "Swift": include example.swift - when "Pascal": include example.pas include popups.pug diff --git a/views/sitemap.pug b/views/sitemap.pug index dbc3921b5..bf4698e9c 100644 --- a/views/sitemap.pug +++ b/views/sitemap.pug @@ -3,30 +3,3 @@ urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9") url loc | https://godbolt.org - priority - | 0.8 - url - loc - | https://gcc.godbolt.org - url - loc - | https://rust.godbolt.org - url - loc - | https://d.godbolt.org - url - loc - | https://go.godbolt.org - url - loc - | https://cppx.godbolt.org - url - loc - | https://ispc.godbolt.org - url - loc - | https://haskell.godbolt.org - url - loc - | https://swift.godbolt.org - diff --git a/views/templates.pug b/views/templates.pug index b100ee2ea..4eb31f2cb 100644 --- a/views/templates.pug +++ b/views/templates.pug @@ -11,6 +11,8 @@ span.glyphicon.glyphicon-open button.btn.btn-default.btn-sm.conformance(title="Add a new conformance view") span.glyphicon.glyphicon-list-alt + .btn-group.btn-group-sm.pull-right + select.change-language(title="Change this editor's (and associated panels) language" disabled=embedded) .monaco-placeholder #compiler |