From 7e1835fa0f376a81ef47b95bd7a1a9dd59d0cb9b Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Sat, 31 Jul 2021 04:29:37 +0800 Subject: [PATCH] Add support for Ruby (#2816) --- .github/labeler.yml | 3 + etc/config/ruby.amazon.properties | 17 ++++++ etc/config/ruby.defaults.properties | 6 ++ etc/scripts/ruby/disasm.rb | 38 ++++++++++++ examples/ruby/default.rb | 4 ++ lib/compilers/_all.js | 1 + lib/compilers/ruby.js | 95 +++++++++++++++++++++++++++++ lib/languages.js | 7 +++ static/modes/asmruby-mode.js | 59 ++++++++++++++++++ static/panes/compiler.js | 1 + webpack.config.esm.js | 2 +- 11 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 etc/config/ruby.amazon.properties create mode 100644 etc/config/ruby.defaults.properties create mode 100644 etc/scripts/ruby/disasm.rb create mode 100644 examples/ruby/default.rb create mode 100644 lib/compilers/ruby.js create mode 100644 static/modes/asmruby-mode.js diff --git a/.github/labeler.yml b/.github/labeler.yml index 06e10574e..3fcd542ed 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -74,6 +74,9 @@ lang-pascal: lang-python: - lib/compilers/python.js - etc/config/python.*.properties +lang-ruby: + - lib/compilers/ruby.js + - etc/config/ruby.*.properties lang-rust: - lib/compilers/rust.js - etc/config/rust.*.properties diff --git a/etc/config/ruby.amazon.properties b/etc/config/ruby.amazon.properties new file mode 100644 index 000000000..f83c332f5 --- /dev/null +++ b/etc/config/ruby.amazon.properties @@ -0,0 +1,17 @@ +compilers=&ruby +defaultCompiler=ruby302 + +group.ruby.compilers=ruby302:ruby274:ruby268:ruby259 +group.ruby.isSemVer=true +group.ruby.baseName=Ruby +group.ruby.groupName=Ruby YARV +group.ruby.compilerType=ruby + +compiler.ruby302.semver=3.0.2 +compiler.ruby302.exe=/opt/compiler-explorer/ruby-3.0.2/bin/ruby +compiler.ruby274.semver=2.7.4 +compiler.ruby274.exe=/opt/compiler-explorer/ruby-2.7.4/bin/ruby +compiler.ruby268.semver=2.6.8 +compiler.ruby268.exe=/opt/compiler-explorer/ruby-2.6.8/bin/ruby +compiler.ruby259.semver=2.5.9 +compiler.ruby259.exe=/opt/compiler-explorer/ruby-2.5.9/bin/ruby diff --git a/etc/config/ruby.defaults.properties b/etc/config/ruby.defaults.properties new file mode 100644 index 000000000..d70f3c526 --- /dev/null +++ b/etc/config/ruby.defaults.properties @@ -0,0 +1,6 @@ +compilers=/usr/bin/ruby +supportsBinary=false +supportsExecute=true +interpreted=true +compilerType=ruby +disasmScript= diff --git a/etc/scripts/ruby/disasm.rb b/etc/scripts/ruby/disasm.rb new file mode 100644 index 000000000..77b7f2f71 --- /dev/null +++ b/etc/scripts/ruby/disasm.rb @@ -0,0 +1,38 @@ +# Copyright (c) 2021, 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. + +require 'optparse' + +options = {} +OptionParser.new do |opts| + opts.banner = "Usage: disasm.rb [options]" + + opts.on("-i", "--inputfile FILE", "Use FILE as input") { |v| options[:inputfile] = v } + opts.on("-o", "--outputfile FILE", "Use FILE as output") { |v| options[:outputfile] = v } + opts.on("-n", "--fname NAME", "Use NAME as input file name") { |v| options[:fname] = v } +end.parse! + +src = File.read(options[:inputfile]) +insns = RubyVM::InstructionSequence.compile(src, options[:fname] || options[:inputfile], options[:inputfile], 1) +File.write(options[:outputfile], insns.disassemble) diff --git a/examples/ruby/default.rb b/examples/ruby/default.rb new file mode 100644 index 000000000..a75c5da7d --- /dev/null +++ b/examples/ruby/default.rb @@ -0,0 +1,4 @@ +# Type your code here, or load an example. +def square(num) + num * num +end \ No newline at end of file diff --git a/lib/compilers/_all.js b/lib/compilers/_all.js index ce1e3c81a..01c6d29cb 100644 --- a/lib/compilers/_all.js +++ b/lib/compilers/_all.js @@ -61,6 +61,7 @@ export { PascalWinCompiler } from './pascal-win'; export { PPCICompiler } from './ppci'; export { PtxAssembler } from './ptxas'; export { PythonCompiler } from './python'; +export { RubyCompiler } from './ruby'; export { RustCompiler } from './rust'; export { RustcCgGCCCompiler } from './rustc-cg-gcc'; export { MrustcCompiler } from './mrustc'; diff --git a/lib/compilers/ruby.js b/lib/compilers/ruby.js new file mode 100644 index 000000000..fae613667 --- /dev/null +++ b/lib/compilers/ruby.js @@ -0,0 +1,95 @@ +// Copyright (c) 2021, 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 { BaseCompiler } from '../base-compiler'; +import { resolvePathFromAppRoot } from '../utils'; + +import { BaseParser } from './argument-parsers'; + +export class RubyCompiler extends BaseCompiler { + static get key() { return 'ruby'; } + + constructor(compilerInfo, env) { + super(compilerInfo, env); + this.compiler.demangler = null; + this.demanglerClass = null; + } + + processAsm(result, filters) { + const lineRe = /\(\s*(\d+)\)(?:\[[^\]]+\])?$/; + const fileRe = /ISeq:.*?@(.*?):(\d+) /; + const baseFile = path.basename(this.compileFilename); + + const bytecodeLines = result.asm.split('\n'); + + const bytecodeResult = []; + let lastFile = null; + let lastLineNo = null; + + for (const line of bytecodeLines) { + const match = line.match(lineRe); + + if (match) { + lastLineNo = parseInt(match[1]); + } else if (!line) { + lastFile = null; + lastLineNo = null; + } else { + const fileMatch = line.match(fileRe); + if (fileMatch) { + lastFile = fileMatch[1]; + lastLineNo = parseInt(fileMatch[2]); + } + } + + const file = lastFile == baseFile ? null : lastFile; + const result = {text: line, source: {line: lastLineNo, file: file}}; + bytecodeResult.push(result); + } + + return {asm: bytecodeResult}; + } + + getDisasmScriptPath() { + const script = this.compilerProps('disasmScript'); + + return script || resolvePathFromAppRoot('etc', 'scripts', 'ruby', 'disasm.rb'); + } + + optionsForFilter(filters, outputFilename) { + return [ + this.getDisasmScriptPath(), + '--outputfile', + outputFilename, + '--fname', + path.basename(this.compileFilename), + '--inputfile']; + } + + getArgumentParser() { + return BaseParser; + } +} diff --git a/lib/languages.js b/lib/languages.js index e73fac0ef..77eca064b 100644 --- a/lib/languages.js +++ b/lib/languages.js @@ -235,6 +235,13 @@ export const languages = { alias: [], previewFilter: /^\s*#include/, }, + ruby: { + name: 'Ruby', + monaco: 'ruby', + extensions: ['.rb'], + alias: [], + monacoDisassembly: 'asmruby', + }, }; _.each(languages, (lang, key) => { diff --git a/static/modes/asmruby-mode.js b/static/modes/asmruby-mode.js new file mode 100644 index 000000000..cfed0958d --- /dev/null +++ b/static/modes/asmruby-mode.js @@ -0,0 +1,59 @@ +// Copyright (c) 2021, 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. + +'use strict'; + +var monaco = require('monaco-editor'); + +function definition() { + return { + tokenizer: { + root: [ + [/^(\| )*==.*$/, 'comment'], + [/^(\| )*catch type.*$/, 'comment'], + [/^(\| )*local table.*$/, 'comment'], + [/^(\| )*\[\s*\d+\].*$/, 'comment'], + [/^(\| )*\|-+$/, 'comment'], + [/^((?:\| )*)(\d+)/, ['comment', { token: 'number', next: '@opcode' }]], + [/^((?:\| )*)(\d+)(\s+)/, ['comment', 'number', { token: '', next: '@opcode' }]], + ], + + opcode: [ + [/[a-z_]\w*\s*$/, { token: 'keyword', next: '@root' }], + [/([a-z_]\w*)(\s+)/, ['keyword', { token: '', next: '@arguments' }]], + ], + + arguments: [ + [/(.*?)(\(\s*\d+\)(?:\[[^\]]+\])?)$/, ['', { token: 'comment', next: '@root' }]], + [/.*$/, { token: '', next: '@root' }], + ], + }, + }; +} + +var def = definition(); +monaco.languages.register({ id: 'asmruby' }); +monaco.languages.setMonarchTokensProvider('asmruby', def); + +module.exports = def; diff --git a/static/panes/compiler.js b/static/panes/compiler.js index 4ccc4f7b0..c94e8187c 100644 --- a/static/panes/compiler.js +++ b/static/panes/compiler.js @@ -44,6 +44,7 @@ var CompilerPicker = require('../compiler-picker'); var Settings = require('../settings'); require('../modes/asm-mode'); +require('../modes/asmruby-mode'); require('../modes/ptx-mode'); var OpcodeCache = new LruCache({ diff --git a/webpack.config.esm.js b/webpack.config.esm.js index 32dc6712c..515071145 100644 --- a/webpack.config.esm.js +++ b/webpack.config.esm.js @@ -46,7 +46,7 @@ const staticPath = path.join(distPath, 'static'); const webjackJsHack = '.v4.'; const plugins = [ new MonacoEditorWebpackPlugin({ - languages: [ 'cpp', 'go', 'pascal', 'python', 'rust', 'swift', 'java', 'kotlin', 'scala' ], + languages: [ 'cpp', 'go', 'pascal', 'python', 'rust', 'swift', 'java', 'kotlin', 'scala', 'ruby' ], filename: isDev ? '[name].worker.js' : `[name]${webjackJsHack}worker.[contenthash].js`, }), new CopyWebpackPlugin([