diff options
author | Sebastian <s_rath@xpresso24.com> | 2019-10-03 00:04:51 -0400 |
---|---|---|
committer | Austin Morton <austinpmorton@gmail.com> | 2019-10-30 19:51:38 -0400 |
commit | 78b3381c84a238cc8be48e5685d74ceec5b36765 (patch) | |
tree | ec05ec7a8b33b653b7ac289da401d3eb636e10c1 | |
parent | 568f906322377817c400f09f0e7212e8ba800a11 (diff) | |
download | compiler-explorer-78b3381c84a238cc8be48e5685d74ceec5b36765.tar.gz compiler-explorer-78b3381c84a238cc8be48e5685d74ceec5b36765.zip |
* Add Python support (#1612)
-rw-r--r-- | CONTRIBUTORS.md | 2 | ||||
-rw-r--r-- | etc/config/python.defaults.properties | 21 | ||||
-rw-r--r-- | etc/scripts/dis_all.py | 132 | ||||
-rw-r--r-- | examples/python/default.py | 2 | ||||
-rw-r--r-- | lib/compilers/python.js | 86 | ||||
-rw-r--r-- | lib/languages.js | 6 |
6 files changed, 248 insertions, 1 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 59e18e265..7ad5cae63 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -78,4 +78,4 @@ From oldest to newest contributor, we would like to thank: - [Matt Hammerly](https://github.com/mhammerly) - [Christian Vonrüti](https://github.com/alshain) - [Alessandro Vergani](https://github.com/Loghorn) - +- [Sebastian Rath](https://github.com/seb-mtl) diff --git a/etc/config/python.defaults.properties b/etc/config/python.defaults.properties new file mode 100644 index 000000000..90511e100 --- /dev/null +++ b/etc/config/python.defaults.properties @@ -0,0 +1,21 @@ +compilers=&python3 + +group.python3.compilers=python36:python37:python38 +compiler.python36.name=Python 3.6 +compiler.python36.exe=python3.6 +compiler.python37.name=Python 3.7 +compiler.python37.exe=python3.7 +compiler.python38.name=Python 3.8 +compiler.python38.exe=python3.8 + +supportsBinary=false +compilerType=python +objdumper= +demangler= +postProcess= +options= +supportsBinary=false +needsMulti=false +supportsExecute=false +stubText=def main(): +disasmScript= diff --git a/etc/scripts/dis_all.py b/etc/scripts/dis_all.py new file mode 100644 index 000000000..65472ad18 --- /dev/null +++ b/etc/scripts/dis_all.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Sebastian Rath +# 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 os +import sys +import dis +import argparse +import traceback + +from dis import dis, disassemble, distb, _have_code, _disassemble_bytes, _try_compile + +parser = argparse.ArgumentParser(description='Disassembles Python source code given by an input file and writes the output to a file') +parser.add_argument('-i', '--inputfile', type=str, + help='Input source code file (*.py)') +parser.add_argument('-o', '--outputfile', type=str, + help='Optional output file to write output (or error message if syntax error).', + default='') + + +def _disassemble_recursive(co, depth=None): + disassemble(co) + if depth is None or depth > 0: + if depth is not None: + depth = depth - 1 + for x in co.co_consts: + if hasattr(x, 'co_code'): + print() + print("Disassembly of %r:" % (x,)) + _disassemble_recursive(x, depth=depth) + + +def _disassemble_str(source, **kwargs): + """Compile the source string, then disassemble the code object.""" + _disassemble_recursive(_try_compile(source, '<dis>'), **kwargs) + + +# This function is copied from Py 3.7 and compatible with Py 3.3, 3.4, 3.5 to support recursive diassemble +def dis37(x=None, depth=None): + """Disassemble classes, methods, functions, and other compiled objects. + + With no argument, disassemble the last traceback. + + Compiled objects currently include generator objects, async generator + objects, and coroutine objects, all of which store their code object + in a special attribute. + """ + if x is None: + distb() + return + # Extract functions from methods. + if hasattr(x, '__func__'): + x = x.__func__ + # Extract compiled code objects from... + if hasattr(x, '__code__'): # ...a function, or + x = x.__code__ + elif hasattr(x, 'gi_code'): #...a generator object, or (added in Py 3.5) + x = x.gi_code + elif hasattr(x, 'ag_code'): #...an asynchronous generator object, or (added in Py 3.7) + x = x.ag_code + elif hasattr(x, 'cr_code'): #...a coroutine. (added in Py 3.7) + x = x.cr_code + # Perform the disassembly. + if hasattr(x, '__dict__'): # Class or module (added in Py 3.7) + items = sorted(x.__dict__.items()) + for name, x1 in items: + if isinstance(x1, _have_code): + print("Disassembly of %s:" % name) + try: + dis(x1, depth=depth) + except TypeError as msg: + print("Sorry:", msg) + print() + elif hasattr(x, 'co_code'): # Code object + _disassemble_recursive(x, depth=depth) + elif isinstance(x, (bytes, bytearray)): # Raw bytecode + _disassemble_bytes(x) + elif isinstance(x, str): # Source code + _disassemble_str(x, depth=depth) + else: + raise TypeError("don't know how to disassemble %s objects" % + type(x).__name__) + + +if __name__ == '__main__': + args = parser.parse_args() + + if not args.inputfile: + parser.print_help(sys.stderr) + sys.exit(1) + + with open(args.inputfile, 'r', encoding='utf8') as fp: + source = fp.read() + + name = os.path.basename(args.inputfile) + try: + code = compile(source, name, 'exec') + except Exception as e: + # redirect any other by compile(..) to stderr in order to hide traceback of this script + sys.stderr.write(''.join(traceback.format_exception_only(type(e), e))) + sys.exit(255) + + if args.outputfile: + sys.stdout = open(args.outputfile, 'w', encoding='utf8') + + if sys.version_info < (3, 7): + # Any Python version older than 3.7 doesn't support recursive diassembly, + # so we call our own function + dis37(code) + else: + dis(code) diff --git a/examples/python/default.py b/examples/python/default.py new file mode 100644 index 000000000..4f023ea04 --- /dev/null +++ b/examples/python/default.py @@ -0,0 +1,2 @@ +def main(): + pass
\ No newline at end of file diff --git a/lib/compilers/python.js b/lib/compilers/python.js new file mode 100644 index 000000000..15f42d0b8 --- /dev/null +++ b/lib/compilers/python.js @@ -0,0 +1,86 @@ +// Copyright (c) 2019, Sebastian Rath +// 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 BaseCompiler = require('../base-compiler'), + argumentParsers = require("./argument-parsers"), + path = require('path'); + +class PythonCompiler extends BaseCompiler { + + constructor(compilerInfo, env) { + super(compilerInfo, env); + this.compiler.demangler = null; + this.demanglerClass = null; + } + + // eslint-disable-next-line no-unused-vars + processAsm(result, filters) { + const lineRe = /^\s{0,4}([0-9]+)(.*)/; + + const bytecodeLines = result.asm.split("\n"); + + var bytecodeResult = []; + var lastLineNo = null; + var sourceLoc = null; + + bytecodeLines.forEach(line => { + var match = line.match(lineRe); + + if (match) { + const lineno = parseInt(match[1]); + sourceLoc = {line: lineno, file: null}; + lastLineNo = lineno; + } else if (!line) { + sourceLoc = {line: null, file: null}; + lastLineNo = null; + } else { + sourceLoc = {line: lastLineNo, file: null}; + } + + bytecodeResult.push({text: line, source: sourceLoc}); + }); + + return bytecodeResult; + } + + getDisasmScriptPath() { + const script = this.compilerProps('disasmScript'); + + return script || path.resolve(__dirname, '..', '..', 'etc', 'scripts', 'dis_all.py'); + } + + optionsForFilter(filters, outputFilename) { + return ['-I', + this.getDisasmScriptPath(), + '--outputfile', + outputFilename, + '--inputfile']; + } + + getArgumentParser() { + return argumentParsers.Base; + } +} + +module.exports = PythonCompiler; diff --git a/lib/languages.js b/lib/languages.js index e77b337e7..164b5055a 100644 --- a/lib/languages.js +++ b/lib/languages.js @@ -114,6 +114,12 @@ const languages = { extensions: ['.ml', '.mli'], alias: [] }, + python: { + name: 'Python', + monaco: 'python', + extensions: ['.py'], + alias: [] + }, swift: { name: 'Swift', monaco: 'swift', |