diff --git a/.github/labeler.yml b/.github/labeler.yml index 682cd3471..f7c548794 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -134,6 +134,9 @@ lang-pony: lang-python: - lib/compilers/python.js - etc/config/python.*.properties +lang-racket: + - lib/compilers/racket.ts + - etc/config/racket.*.properties lang-ruby: - lib/compilers/ruby.js - etc/config/ruby.*.properties diff --git a/docs/AddingALanguage.md b/docs/AddingALanguage.md index 6881ec696..02c763da4 100644 --- a/docs/AddingALanguage.md +++ b/docs/AddingALanguage.md @@ -19,7 +19,8 @@ If you want to add a new language to the site, you should follow this steps: - If the language is supported by Monaco Editor (You can find the list [here](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages)), you should add it to the list of - languages inside the `MonacoEditorWebpackPlugin` config in `webpack.config.js` + languages inside the `MonacoEditorWebpackPlugin` config in + `webpack.config.esm.js` - If not, you should implement your own language mode; see `static/modes/asm-mode.js` as an example. Don't forget to _require_ your mode file in `static/modes/_all.ts`, in alphabetical order - `language-key` is how your language will be referred internally by the code. In the rest of this document, replace diff --git a/etc/config/racket.amazon.properties b/etc/config/racket.amazon.properties new file mode 100644 index 000000000..cead970ba --- /dev/null +++ b/etc/config/racket.amazon.properties @@ -0,0 +1,12 @@ +compilers=&racket +defaultCompiler=racket86 + +group.racket.groupName=Racket +group.racket.compilers=racket86 +group.racket.isSemVer=true +group.racket.baseName=Racket +group.racket.licenseName=MIT and Apache 2 +group.racket.licenseLink=https://docs.racket-lang.org/license/ +compiler.racket86.semver=8.6 +compiler.racket86.exe=/opt/compiler-explorer/racket/racket-8.6/bin/racket +compiler.racket86.raco=/opt/compiler-explorer/racket/racket-8.6/bin/raco diff --git a/etc/config/racket.defaults.properties b/etc/config/racket.defaults.properties new file mode 100644 index 000000000..525cae32f --- /dev/null +++ b/etc/config/racket.defaults.properties @@ -0,0 +1,13 @@ +compilers=racket +compilerType=racket + +versionFlag=--version +versionRe=Racket v\d+\.\d+(\.\d+)? + +interpreted=true +supportsBinary=false +supportsExecute=false + +compiler.racket.name=Racket +compiler.racket.exe=/usr/local/bin/racket +compiler.racket.raco=/usr/local/bin/raco diff --git a/examples/racket/default.rkt b/examples/racket/default.rkt new file mode 100644 index 000000000..6a72e0d36 --- /dev/null +++ b/examples/racket/default.rkt @@ -0,0 +1,5 @@ +#lang racket/base + +;; Type your code here, or load an example. +(define (square num) + (* num num)) diff --git a/lib/compilers/_all.js b/lib/compilers/_all.js index 4056ecac7..fcd31fdda 100644 --- a/lib/compilers/_all.js +++ b/lib/compilers/_all.js @@ -79,6 +79,7 @@ export {PonyCompiler} from './pony'; export {PPCICompiler} from './ppci'; export {PtxAssembler} from './ptxas'; export {PythonCompiler} from './python'; +export {RacketCompiler} from './racket'; export {RGACompiler} from './rga'; export {RubyCompiler} from './ruby'; export {RustcCgGCCCompiler} from './rustc-cg-gcc'; diff --git a/lib/compilers/racket.ts b/lib/compilers/racket.ts new file mode 100644 index 000000000..d64b5d5a0 --- /dev/null +++ b/lib/compilers/racket.ts @@ -0,0 +1,122 @@ +// Copyright (c) 2022, Compiler Explorer Authors +// 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. + +import path from 'path'; + +import {CompilationResult, ExecutionOptions} from '../../types/compilation/compilation.interfaces'; +import {ParseFilters} from '../../types/features/filters.interfaces'; +import {BaseCompiler} from '../base-compiler'; +import {logger} from '../logger'; + +export class RacketCompiler extends BaseCompiler { + private raco: string; + + static get key() { + return 'racket'; + } + + constructor(info, env) { + // Disable output filters, as they currently don't do anything + if (!info.disabledFilters) { + info.disabledFilters = ['labels', 'directives', 'commentOnly', 'trim']; + } + super(info, env); + this.raco = this.compilerProps(`compiler.${this.compiler.id}.raco`); + } + + override optionsForFilter(filters: ParseFilters, outputFilename: string, userOptions?: string[]): string[] { + // We currently always compile to bytecode first and then decompile. + // Forcing `binary` on like this ensures `objdump` will be called for + // the decompilation phase. + filters.binary = true; + + return []; + } + + override supportsObjdump(): boolean { + return true; + } + + override getSharedLibraryPathsAsArguments(libraries: object[], libDownloadPath?: string): string[] { + return []; + } + + override async runCompiler( + compiler: string, + options: string[], + inputFilename: string, + execOptions: ExecutionOptions, + ): Promise { + if (!execOptions) { + execOptions = this.getDefaultExecOptions(); + } + + if (!execOptions.customCwd) { + execOptions.customCwd = path.dirname(inputFilename); + } + + // Compile to bytecode via `raco make` + options.unshift('make'); + const makeResult = await this.exec(this.raco, options, execOptions); + + return this.transformToCompilationResult(makeResult, inputFilename); + } + + override getOutputFilename(dirPath: string, outputFilebase: string, key?: any): string { + return path.join(dirPath, 'compiled', `${this.compileFilename.replace('.', '_')}.zo`); + } + + override async objdump( + outputFilename: any, + result: any, + maxSize: number, + intelAsm: any, + demangle: any, + filters: ParseFilters, + ): Promise { + // Decompile to assembly via `raco decompile` with `disassemble` package + const execOptions: ExecutionOptions = { + maxOutput: maxSize, + customCwd: (result.dirPath as string) || path.dirname(outputFilename), + }; + const decompileResult = await this.exec(this.raco, ['decompile', outputFilename], execOptions); + + if (decompileResult.code) { + logger.error('Error decompiling via `raco decompile`', decompileResult); + result.asm = ``; + } + + result.objdumpTime = decompileResult.execTime; + result.asm = this.postProcessObjdumpOutput(decompileResult.stdout); + + return result; + } + + override processAsm(result: any, filters: any, options: any) { + // TODO: Process and highlight decompiled output + return { + asm: [{text: result.asm}], + }; + } +} diff --git a/lib/languages.ts b/lib/languages.ts index 303e999bb..7feacbc0c 100644 --- a/lib/languages.ts +++ b/lib/languages.ts @@ -471,6 +471,17 @@ const definitions: Record = { previewFilter: null, monacoDisassembly: null, }, + racket: { + name: 'Racket', + monaco: 'scheme', + extensions: ['.rkt'], + alias: [], + logoUrl: 'racket.svg', + logoUrlDark: null, + formatter: null, + previewFilter: null, + monacoDisassembly: 'scheme', + }, ruby: { name: 'Ruby', monaco: 'ruby', diff --git a/types/languages.interfaces.ts b/types/languages.interfaces.ts index d99e550b7..4bae85386 100644 --- a/types/languages.interfaces.ts +++ b/types/languages.interfaces.ts @@ -62,6 +62,7 @@ export type LanguageKey = | 'pascal' | 'pony' | 'python' + | 'racket' | 'ruby' | 'rust' | 'scala' diff --git a/views/resources/logos/racket.svg b/views/resources/logos/racket.svg new file mode 100644 index 000000000..d5c05d0bf --- /dev/null +++ b/views/resources/logos/racket.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/webpack.config.esm.js b/webpack.config.esm.js index 631e9c257..a65e4e2eb 100644 --- a/webpack.config.esm.js +++ b/webpack.config.esm.js @@ -64,6 +64,7 @@ const plugins = [ 'dart', 'typescript', 'solidity', + 'scheme', ], filename: isDev ? '[name].worker.js' : `[name]${webjackJsHack}worker.[contenthash].js`, }),