diff --git a/java2python/compiler/visitor.py b/java2python/compiler/visitor.py index f62e53e..3e7721e 100644 --- a/java2python/compiler/visitor.py +++ b/java2python/compiler/visitor.py @@ -14,7 +14,7 @@ from functools import reduce, partial from itertools import ifilter, ifilterfalse, izip, tee -from logging import debug, warn +from logging import debug, warn, warning from re import compile as recompile, sub as resub from java2python.lang import tokens @@ -36,6 +36,11 @@ class Base(object): def accept(self, node, memo): """ Accept a node, possibly creating a child visitor. """ tokType = tokens.map.get(node.token.type) + if node and node.token: + tokType = tokens.map.get(node.token.type) + else: + warning('No child') + return missing = lambda node, memo:self call = getattr(self, 'accept{0}'.format(tokens.title(tokType)), missing) if call is missing: @@ -79,7 +84,11 @@ def walk(self, tree, memo=None): return memo = Memo() if memo is None else memo comIns = self.insertComments - comIns(self, tree, tree.tokenStartIndex, memo) + try: + comIns(self, tree, tree.tokenStartIndex, memo) + except: + warning('No comments inserted') + visitor = self.accept(tree, memo) if visitor: for child in tree.children: @@ -452,7 +461,8 @@ def acceptFor(self, node, memo): else: whileStat.expr.walk(cond, memo) whileBlock = self.factory.methodContent(parent=self) - if not node.firstChildOfType(tokens.BLOCK_SCOPE).children: + if (not node.firstChildOfType(tokens.BLOCK_SCOPE) or + not node.firstChildOfType(tokens.BLOCK_SCOPE).children): self.factory.expr(left='pass', parent=whileBlock) else: whileBlock.walk(node.firstChildOfType(tokens.BLOCK_SCOPE), memo) @@ -524,7 +534,7 @@ def acceptSwitch(self, node, memo): lblNode = node.firstChildOfType(tokens.SWITCH_BLOCK_LABEL_LIST) caseNodes = lblNode.children # empty switch statement - if not len(caseNodes): + if not caseNodes: return # we have at least one node... parExpr = self.factory.expr(parent=self) @@ -547,7 +557,7 @@ def acceptSwitch(self, node, memo): caseContent = self.factory.methodContent(parent=self) for child in caseNode.children[1:]: caseContent.walk(child, memo) - if not caseNode.children[1:]: + if not caseNode.children or not caseNode.children[1:]: self.factory.expr(left='pass', parent=caseContent) if isDefault: if isFirst: @@ -619,7 +629,7 @@ def acceptWhile(self, node, memo): parNode, blkNode = node.children whileStat = self.factory.statement('while', fs=FS.lsrc, parent=self) whileStat.expr.walk(parNode, memo) - if not blkNode.children: + if not blkNode or not blkNode.children: self.factory.expr(left='pass', parent=whileStat) else: whileStat.walk(blkNode, memo) diff --git a/java2python/lang/selector.py b/java2python/lang/selector.py index 22b531a..099149d 100644 --- a/java2python/lang/selector.py +++ b/java2python/lang/selector.py @@ -160,7 +160,7 @@ def __init__(self, key, value=None): self.value = value def __call__(self, tree): - if tree.token.type == self.key: + if tree.token and tree.token.type == self.key: if self.value is None or self.value == tree.token.text: yield tree diff --git a/java2python/main.py b/java2python/main.py new file mode 100644 index 0000000..9eca94f --- /dev/null +++ b/java2python/main.py @@ -0,0 +1,263 @@ +#!/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 + + +def main(): + runMain(configScript(sys.argv[1:])) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/setup.py b/setup.py index 210d623..2f2090c 100644 --- a/setup.py +++ b/setup.py @@ -9,8 +9,8 @@ This version requires Python 2.7. """ - -from distutils.core import setup +from setuptools import find_packages, setup +# from distutils.core import setup from os import path, listdir @@ -72,7 +72,7 @@ def doc_files(): '*.tokens', ] }, - + entry_points={'console_scripts':['j2py = java2python.main:main']}, scripts=[ 'bin/j2py', ],