From 8a84fc11b7ad2dabd69c3dc60ffe5523a676cd56 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Sat, 3 Feb 2024 22:40:25 -0600 Subject: [PATCH] setup the project, fix defs, and suppress exceptions need to figure out how to run the tests asap --- .idea/.gitignore | 3 + .idea/inspectionProfiles/Project_Default.xml | 31 +++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/java2python.iml | 17 ++ .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + j2py.py | 259 ++++++++++++++++++ java2python/compiler/visitor.py | 25 +- java2python/lang/JavaLexer.py | 8 + java2python/lang/JavaParser.py | 7 + setup.cfg | 4 + 12 files changed, 371 insertions(+), 7 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/java2python.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 j2py.py create mode 100644 setup.cfg diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7eebd73 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/java2python.iml b/.idea/java2python.iml new file mode 100644 index 0000000..abc6379 --- /dev/null +++ b/.idea/java2python.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7ba73c2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9a770cd --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/j2py.py b/j2py.py new file mode 100644 index 0000000..6eb1a40 --- /dev/null +++ b/j2py.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" j2py -> Java to Python compiler script. + +This is all very ordinary. We import the package bits, open and read +a file, translate it, and write it out. + +""" +import sys +from argparse import ArgumentParser, ArgumentTypeError +from collections import defaultdict +from logging import _levelNames as logLevels, exception, warning, info, basicConfig +from os import path, makedirs +from time import time + +from java2python.compiler import Module, buildAST, transformAST +from java2python.config import Config +from java2python.lib import escapes + + +version = '0.5.1' + + +def logLevel(value): + """ Returns a valid logging level or raises and exception. """ + msg = 'invalid loglevel: %r' + try: + lvl = int(value) + except (ValueError, ): + name = value.upper() + if name not in logLevels: + raise ArgumentTypeError(msg % value) + lvl = logLevels[name] + else: + if lvl not in logLevels: + raise ArgumentTypeError(msg % value) + return lvl + + +def configFromDir(inname, dirname): + """ Returns a file name from the given config directory. """ + name = path.join(dirname, path.basename(path.splitext(inname)[0])) + return '%s.py' % path.abspath(name) + + +def runMain(options): + """ Runs our main function with profiling if indicated by options. """ + if options.profile: + import cProfile, pstats + prof = cProfile.Profile() + prof.runcall(runOneOrMany, options) + stats = pstats.Stats(prof, stream=sys.stderr) + stats.strip_dirs().sort_stats('cumulative') + stats.print_stats().print_callers() + return 0 + else: + return runOneOrMany(options) + +def runOneOrMany(options): + """ Runs our main transformer with each of the input files. """ + infile, outfile = options.inputfile, options.outputfile + + if infile and not isinstance(infile, file) and path.isdir(infile): + if outfile and not isinstance(outfile, file) and not path.isdir(outfile): + warning('Must specify output directory or stdout when using input directory.') + return 2 + def walker(arg, dirname, files): + for name in [name for name in files if name.endswith('.java')]: + fullname = path.join(dirname, name) + options.inputfile = fullname + info('opening %s', fullname) + if outfile and outfile != '-' and not isinstance(outfile, file): + full = path.abspath(path.join(outfile, fullname)) + head, tail = path.split(full) + tail = path.splitext(tail)[0] + '.py' + if not path.exists(head): + makedirs(head) + options.outputfile = path.join(head, tail) + runTransform(options) + path.walk(infile, walker, None) + return 0 + else: + return runTransform(options) + + +def runTransform(options): + """ Compile the indicated java source with the given options. """ + timed = defaultdict(time) + timed['overall'] + + filein = fileout = filedefault = '-' + if options.inputfile and not isinstance(options.inputfile, file): + filein = options.inputfile + if options.outputfile and not isinstance(options.outputfile, file): + fileout = options.outputfile + elif fileout != filedefault: + fileout = '%s.py' % (path.splitext(filein)[0]) + + configs = options.configs + if options.configdirs and not isinstance(filein, file): + for configdir in options.configdirs: + dirname = configFromDir(filein, configdir) + if path.exists(dirname): + configs.insert(0, dirname) + if options.includedefaults: + configs.insert(0, 'java2python.config.default') + + try: + if filein != '-': + source = open(filein).read() + else: + source = sys.stdin.read() + except (IOError, ), exc: + code, msg = exc.args[0:2] + print 'IOError: %s.' % (msg, ) + return code + + timed['comp'] + try: + tree = buildAST(source) + except (Exception, ), exc: + exception('exception while parsing') + return 1 + timed['comp_finish'] + + config = Config(configs) + timed['xform'] + transformAST(tree, config) + timed['xform_finish'] + + timed['visit'] + module = Module(config) + module.sourceFilename = path.abspath(filein) if filein != '-' else None + module.name = path.splitext(path.basename(filein))[0] if filein != '-' else '' + module.walk(tree) + timed['visit_finish'] + + timed['encode'] + source = unicode(module) + timed['encode_finish'] + timed['overall_finish'] + + if options.lexertokens: + for idx, tok in enumerate(tree.parser.input.tokens): + print >> sys.stderr, '{0} {1}'.format(idx, tok) + print >> sys.stderr + + if options.javaast: + tree.dump(sys.stderr) + print >> sys.stderr + + if options.pytree: + module.dumpRepr(sys.stderr) + print >> sys.stderr + + if not options.skipsource: + if fileout == filedefault: + output = sys.stdout + else: + output = open(fileout, 'w') + module.name = path.splitext(filein)[0] if filein != '-' else '' + print >> output, source + + if not options.skipcompile: + try: + compile(source, '', 'exec') + except (SyntaxError, ), ex: + warning('Generated source has invalid syntax. %s', ex) + else: + info('Generated source has valid syntax.') + + info('Parse: %.4f seconds', timed['comp_finish'] - timed['comp']) + info('Visit: %.4f seconds', timed['visit_finish'] - timed['visit']) + info('Transform: %.4f seconds', timed['xform_finish'] - timed['xform']) + info('Encode: %.4f seconds', timed['encode_finish'] - timed['encode']) + info('Total: %.4f seconds', timed['overall_finish'] - timed['overall']) + return 0 + + +def isWindows(): + """ True if running on Windows. """ + return sys.platform.startswith('win') + + +def configLogging(loglevel): + """ Configure the logging package. """ + fmt = '# %(levelname)s %(funcName)s: %(message)s' + basicConfig(level=loglevel, format=fmt) + + +def configColors(nocolor): + """ Configure the color escapes. """ + if isWindows() or nocolor: + escapes.clear() + + +def configScript(argv): + """ Return an options object from the given argument sequence. """ + parser = ArgumentParser( + description='Translate Java source code to Python.', + epilog='Refer to https://github.com/natural/java2python for docs and support.' + ) + + add = parser.add_argument + add(dest='inputfile', nargs='?', + help='Read from INPUT. May use - for stdin (default).', + metavar='INPUT', default=None) + add(dest='outputfile', nargs='?', + help='Write to OUTPUT. May use - for stdout (default).', + metavar='OUTPUT', default=None) + add('-c', '--config', dest='configs', + help='Use CONFIG file or module. May be repeated.', + metavar='CONFIG', default=[], action='append') + add('-d', '--config-dir', dest='configdirs', + help='Use DIR to match input filename with config filename.', + metavar='DIR', default=[], action='append') + add('-f', '--profile', dest='profile', + help='Profile execution and print results to stderr.', + default=False, action='store_true') + add('-j', '--java-ast', dest='javaast', + help='Print java source AST tree to stderr.', + default=False, action='store_true') + add('-k', '--skip-compile', dest='skipcompile', + help='Skip compile check on translated source.', + default=False, action='store_true') + add('-l', '--log-level', dest='loglevel', + help='Set log level by name or value.', + default='WARN', type=logLevel) + add('-n', '--no-defaults', dest='includedefaults', + help='Ignore default configuration module.', + default=True, action='store_false') + add('-p', '--python-tree', dest='pytree', + help='Print python object tree to stderr.', + default=False, action='store_true') + add('-r', '--no-color', dest='nocolor', + help='Disable color output.' +\ + (' No effect on Win OS.' if isWindows() else ''), + default=False, action='store_true') + add('-s', '--skip-source', dest='skipsource', + help='Skip writing translated source; useful when printing trees', + default=False, action='store_true') + add('-t', '--lexer-tokens', dest='lexertokens', + help='Print lexer tokens to stderr.', + default=False, action='store_true') + add('-v', '--version', action='version', version='%(prog)s ' + version) + + ns = parser.parse_args(argv) + if ns.inputfile == '-': + ns.inputfile = sys.stdin + if ns.outputfile == '-': + ns.outputfile = sys.stdout + + configColors(ns.nocolor) + configLogging(ns.loglevel) + return ns + + +if __name__ == '__main__': + sys.exit(runMain(configScript(sys.argv[1:]))) diff --git a/java2python/compiler/visitor.py b/java2python/compiler/visitor.py index f62e53e..c798c7c 100644 --- a/java2python/compiler/visitor.py +++ b/java2python/compiler/visitor.py @@ -35,6 +35,8 @@ class Base(object): def accept(self, node, memo): """ Accept a node, possibly creating a child visitor. """ + if node.token is None: + return # FIXME tokType = tokens.map.get(node.token.type) missing = lambda node, memo:self call = getattr(self, 'accept{0}'.format(tokens.title(tokType)), missing) @@ -45,7 +47,7 @@ def accept(self, node, memo): def insertComments(self, tmpl, tree, index, memo): """ Add comments to the template from tokens in the tree. """ prefix = self.config.last('commentPrefix', '# ') - cache, parser, comTypes = memo.comments, tree.parser, tokens.commentTypes + cache, parser, comTypes = memo.comments, tree.parser if hasattr(tree, "parser") else None, tokens.commentTypes comNew = lambda t:t.type in comTypes and (t.index not in cache) for tok in ifilter(comNew, parser.input.tokens[memo.last:index]): @@ -75,10 +77,12 @@ def stripComment(self, text): def walk(self, tree, memo=None): """ Depth-first visiting of the given AST. """ - if not tree: + if not tree or not hasattr(tree, "type"): return memo = Memo() if memo is None else memo - comIns = self.insertComments + def test(*args, **kwargs): + pass + comIns = test #self.insertComments comIns(self, tree, tree.tokenStartIndex, memo) visitor = self.accept(tree, memo) if visitor: @@ -86,7 +90,7 @@ def walk(self, tree, memo=None): visitor.walk(child, memo) comIns(visitor, child, child.tokenStopIndex, memo) comIns(self, tree, tree.tokenStopIndex, memo) - if tree.isJavaSource: + if getattr(tree, "isJavaSource", False): comIns(self, tree, len(tree.parser.input.tokens), memo) # fixme: we're calling the mutators far too frequently instead # of only once per object after its walk is finished. @@ -742,11 +746,18 @@ def acceptPrePost(self, node, memo): if node.withinExpr: name = node.firstChildOfType(tokens.IDENT).text handler = self.configHandler('VariableNaming') - rename = handler(name) - block = self.parents(lambda x:x.isMethod).next() - if pre: + + left = None # FIXME: handler can be None. + rename = None + if handler is None: left = name else: + rename = handler(name) + + block = self.parents(lambda x: x.isMethod).next() + if pre: + left = name + elif left is None: left = rename block.adopt(factory(fs=FS.l+' = '+FS.r, left=rename, right=name)) self.left = factory(parent=self, fs=FS.l, left=left) diff --git a/java2python/lang/JavaLexer.py b/java2python/lang/JavaLexer.py index 9c1725a..0a73c42 100644 --- a/java2python/lang/JavaLexer.py +++ b/java2python/lang/JavaLexer.py @@ -1,9 +1,17 @@ # $ANTLR 3.1.3 Mar 18, 2009 10:09:25 Java.g 2012-01-29 13:54:05 +import re import sys from antlr3 import * from antlr3.compat import set, frozenset +def version_str_to_tuple(version_str): + """Convert a version string into a tuple of integers.""" + # Extract numeric parts of the version string + numbers = re.findall(r'\d+', version_str) + # Convert numeric strings to integers and return as a tuple + return tuple(map(int, numbers)) + # for convenience in actions HIDDEN = BaseRecognizer.HIDDEN diff --git a/java2python/lang/JavaParser.py b/java2python/lang/JavaParser.py index 28b9c64..57802af 100644 --- a/java2python/lang/JavaParser.py +++ b/java2python/lang/JavaParser.py @@ -1,5 +1,6 @@ # $ANTLR 3.1.3 Mar 18, 2009 10:09:25 Java.g 2012-01-29 13:54:04 +import re import sys from antlr3 import * from antlr3.compat import set, frozenset @@ -7,6 +8,12 @@ from antlr3.tree import * +def version_str_to_tuple(version_str): + """Convert a version string into a tuple of integers.""" + # Extract numeric parts of the version string + numbers = re.findall(r'\d+', version_str) + # Convert numeric strings to integers and return as a tuple + return tuple(map(int, numbers)) # for convenience in actions HIDDEN = BaseRecognizer.HIDDEN diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8bfd5a1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 +