Skip to content

Commit ddf0d19

Browse files
committed
Made visitor.py more robust to lack of node children
1 parent b803756 commit ddf0d19

File tree

4 files changed

+283
-10
lines changed

4 files changed

+283
-10
lines changed

java2python/compiler/visitor.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from functools import reduce, partial
1616
from itertools import ifilter, ifilterfalse, izip, tee
17-
from logging import debug, warn
17+
from logging import debug, warn, warning
1818
from re import compile as recompile, sub as resub
1919

2020
from java2python.lang import tokens
@@ -36,6 +36,11 @@ class Base(object):
3636
def accept(self, node, memo):
3737
""" Accept a node, possibly creating a child visitor. """
3838
tokType = tokens.map.get(node.token.type)
39+
if node and node.token:
40+
tokType = tokens.map.get(node.token.type)
41+
else:
42+
warning('No child')
43+
return
3944
missing = lambda node, memo:self
4045
call = getattr(self, 'accept{0}'.format(tokens.title(tokType)), missing)
4146
if call is missing:
@@ -79,7 +84,11 @@ def walk(self, tree, memo=None):
7984
return
8085
memo = Memo() if memo is None else memo
8186
comIns = self.insertComments
82-
comIns(self, tree, tree.tokenStartIndex, memo)
87+
try:
88+
comIns(self, tree, tree.tokenStartIndex, memo)
89+
except:
90+
warning('No comments inserted')
91+
8392
visitor = self.accept(tree, memo)
8493
if visitor:
8594
for child in tree.children:
@@ -452,7 +461,8 @@ def acceptFor(self, node, memo):
452461
else:
453462
whileStat.expr.walk(cond, memo)
454463
whileBlock = self.factory.methodContent(parent=self)
455-
if not node.firstChildOfType(tokens.BLOCK_SCOPE).children:
464+
if (not node.firstChildOfType(tokens.BLOCK_SCOPE) or
465+
not node.firstChildOfType(tokens.BLOCK_SCOPE).children):
456466
self.factory.expr(left='pass', parent=whileBlock)
457467
else:
458468
whileBlock.walk(node.firstChildOfType(tokens.BLOCK_SCOPE), memo)
@@ -524,7 +534,7 @@ def acceptSwitch(self, node, memo):
524534
lblNode = node.firstChildOfType(tokens.SWITCH_BLOCK_LABEL_LIST)
525535
caseNodes = lblNode.children
526536
# empty switch statement
527-
if not len(caseNodes):
537+
if not caseNodes:
528538
return
529539
# we have at least one node...
530540
parExpr = self.factory.expr(parent=self)
@@ -547,7 +557,7 @@ def acceptSwitch(self, node, memo):
547557
caseContent = self.factory.methodContent(parent=self)
548558
for child in caseNode.children[1:]:
549559
caseContent.walk(child, memo)
550-
if not caseNode.children[1:]:
560+
if not caseNode.children or not caseNode.children[1:]:
551561
self.factory.expr(left='pass', parent=caseContent)
552562
if isDefault:
553563
if isFirst:
@@ -619,7 +629,7 @@ def acceptWhile(self, node, memo):
619629
parNode, blkNode = node.children
620630
whileStat = self.factory.statement('while', fs=FS.lsrc, parent=self)
621631
whileStat.expr.walk(parNode, memo)
622-
if not blkNode.children:
632+
if not blkNode or not blkNode.children:
623633
self.factory.expr(left='pass', parent=whileStat)
624634
else:
625635
whileStat.walk(blkNode, memo)

java2python/lang/selector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def __init__(self, key, value=None):
160160
self.value = value
161161

162162
def __call__(self, tree):
163-
if tree.token.type == self.key:
163+
if tree.token and tree.token.type == self.key:
164164
if self.value is None or self.value == tree.token.text:
165165
yield tree
166166

java2python/main.py

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
""" j2py -> Java to Python compiler script.
4+
5+
This is all very ordinary. We import the package bits, open and read
6+
a file, translate it, and write it out.
7+
8+
"""
9+
import sys
10+
from argparse import ArgumentParser, ArgumentTypeError
11+
from collections import defaultdict
12+
from logging import _levelNames as logLevels, exception, warning, info, basicConfig
13+
from os import path, makedirs
14+
from time import time
15+
16+
from java2python.compiler import Module, buildAST, transformAST
17+
from java2python.config import Config
18+
from java2python.lib import escapes
19+
20+
21+
version = '0.5.1'
22+
23+
24+
def logLevel(value):
25+
""" Returns a valid logging level or raises and exception. """
26+
msg = 'invalid loglevel: %r'
27+
try:
28+
lvl = int(value)
29+
except (ValueError, ):
30+
name = value.upper()
31+
if name not in logLevels:
32+
raise ArgumentTypeError(msg % value)
33+
lvl = logLevels[name]
34+
else:
35+
if lvl not in logLevels:
36+
raise ArgumentTypeError(msg % value)
37+
return lvl
38+
39+
40+
def configFromDir(inname, dirname):
41+
""" Returns a file name from the given config directory. """
42+
name = path.join(dirname, path.basename(path.splitext(inname)[0]))
43+
return '%s.py' % path.abspath(name)
44+
45+
46+
def runMain(options):
47+
""" Runs our main function with profiling if indicated by options. """
48+
if options.profile:
49+
import cProfile, pstats
50+
prof = cProfile.Profile()
51+
prof.runcall(runOneOrMany, options)
52+
stats = pstats.Stats(prof, stream=sys.stderr)
53+
stats.strip_dirs().sort_stats('cumulative')
54+
stats.print_stats().print_callers()
55+
return 0
56+
else:
57+
return runOneOrMany(options)
58+
59+
def runOneOrMany(options):
60+
""" Runs our main transformer with each of the input files. """
61+
infile, outfile = options.inputfile, options.outputfile
62+
63+
if infile and not isinstance(infile, file) and path.isdir(infile):
64+
if outfile and not isinstance(outfile, file) and not path.isdir(outfile):
65+
warning('Must specify output directory or stdout when using input directory.')
66+
return 2
67+
def walker(arg, dirname, files):
68+
for name in [name for name in files if name.endswith('.java')]:
69+
fullname = path.join(dirname, name)
70+
options.inputfile = fullname
71+
info('opening %s', fullname)
72+
if outfile and outfile != '-' and not isinstance(outfile, file):
73+
full = path.abspath(path.join(outfile, fullname))
74+
head, tail = path.split(full)
75+
tail = path.splitext(tail)[0] + '.py'
76+
if not path.exists(head):
77+
makedirs(head)
78+
options.outputfile = path.join(head, tail)
79+
runTransform(options)
80+
path.walk(infile, walker, None)
81+
return 0
82+
else:
83+
return runTransform(options)
84+
85+
86+
def runTransform(options):
87+
""" Compile the indicated java source with the given options. """
88+
timed = defaultdict(time)
89+
timed['overall']
90+
91+
filein = fileout = filedefault = '-'
92+
if options.inputfile and not isinstance(options.inputfile, file):
93+
filein = options.inputfile
94+
if options.outputfile and not isinstance(options.outputfile, file):
95+
fileout = options.outputfile
96+
elif fileout != filedefault:
97+
fileout = '%s.py' % (path.splitext(filein)[0])
98+
99+
configs = options.configs
100+
if options.configdirs and not isinstance(filein, file):
101+
for configdir in options.configdirs:
102+
dirname = configFromDir(filein, configdir)
103+
if path.exists(dirname):
104+
configs.insert(0, dirname)
105+
if options.includedefaults:
106+
configs.insert(0, 'java2python.config.default')
107+
108+
try:
109+
if filein != '-':
110+
source = open(filein).read()
111+
else:
112+
source = sys.stdin.read()
113+
except (IOError, ), exc:
114+
code, msg = exc.args[0:2]
115+
print 'IOError: %s.' % (msg, )
116+
return code
117+
118+
timed['comp']
119+
try:
120+
tree = buildAST(source)
121+
except (Exception, ), exc:
122+
exception('exception while parsing')
123+
return 1
124+
timed['comp_finish']
125+
126+
config = Config(configs)
127+
timed['xform']
128+
transformAST(tree, config)
129+
timed['xform_finish']
130+
131+
timed['visit']
132+
module = Module(config)
133+
module.sourceFilename = path.abspath(filein) if filein != '-' else None
134+
module.name = path.splitext(path.basename(filein))[0] if filein != '-' else '<stdin>'
135+
module.walk(tree)
136+
timed['visit_finish']
137+
138+
timed['encode']
139+
source = unicode(module)
140+
timed['encode_finish']
141+
timed['overall_finish']
142+
143+
if options.lexertokens:
144+
for idx, tok in enumerate(tree.parser.input.tokens):
145+
print >> sys.stderr, '{0} {1}'.format(idx, tok)
146+
print >> sys.stderr
147+
148+
if options.javaast:
149+
tree.dump(sys.stderr)
150+
print >> sys.stderr
151+
152+
if options.pytree:
153+
module.dumpRepr(sys.stderr)
154+
print >> sys.stderr
155+
156+
if not options.skipsource:
157+
if fileout == filedefault:
158+
output = sys.stdout
159+
else:
160+
output = open(fileout, 'w')
161+
module.name = path.splitext(filein)[0] if filein != '-' else '<stdin>'
162+
print >> output, source
163+
164+
if not options.skipcompile:
165+
try:
166+
compile(source, '<string>', 'exec')
167+
except (SyntaxError, ), ex:
168+
warning('Generated source has invalid syntax. %s', ex)
169+
else:
170+
info('Generated source has valid syntax.')
171+
172+
info('Parse: %.4f seconds', timed['comp_finish'] - timed['comp'])
173+
info('Visit: %.4f seconds', timed['visit_finish'] - timed['visit'])
174+
info('Transform: %.4f seconds', timed['xform_finish'] - timed['xform'])
175+
info('Encode: %.4f seconds', timed['encode_finish'] - timed['encode'])
176+
info('Total: %.4f seconds', timed['overall_finish'] - timed['overall'])
177+
return 0
178+
179+
180+
def isWindows():
181+
""" True if running on Windows. """
182+
return sys.platform.startswith('win')
183+
184+
185+
def configLogging(loglevel):
186+
""" Configure the logging package. """
187+
fmt = '# %(levelname)s %(funcName)s: %(message)s'
188+
basicConfig(level=loglevel, format=fmt)
189+
190+
191+
def configColors(nocolor):
192+
""" Configure the color escapes. """
193+
if isWindows() or nocolor:
194+
escapes.clear()
195+
196+
197+
def configScript(argv):
198+
""" Return an options object from the given argument sequence. """
199+
parser = ArgumentParser(
200+
description='Translate Java source code to Python.',
201+
epilog='Refer to https://github.com/natural/java2python for docs and support.'
202+
)
203+
204+
add = parser.add_argument
205+
add(dest='inputfile', nargs='?',
206+
help='Read from INPUT. May use - for stdin (default).',
207+
metavar='INPUT', default=None)
208+
add(dest='outputfile', nargs='?',
209+
help='Write to OUTPUT. May use - for stdout (default).',
210+
metavar='OUTPUT', default=None)
211+
add('-c', '--config', dest='configs',
212+
help='Use CONFIG file or module. May be repeated.',
213+
metavar='CONFIG', default=[], action='append')
214+
add('-d', '--config-dir', dest='configdirs',
215+
help='Use DIR to match input filename with config filename.',
216+
metavar='DIR', default=[], action='append')
217+
add('-f', '--profile', dest='profile',
218+
help='Profile execution and print results to stderr.',
219+
default=False, action='store_true')
220+
add('-j', '--java-ast', dest='javaast',
221+
help='Print java source AST tree to stderr.',
222+
default=False, action='store_true')
223+
add('-k', '--skip-compile', dest='skipcompile',
224+
help='Skip compile check on translated source.',
225+
default=False, action='store_true')
226+
add('-l', '--log-level', dest='loglevel',
227+
help='Set log level by name or value.',
228+
default='WARN', type=logLevel)
229+
add('-n', '--no-defaults', dest='includedefaults',
230+
help='Ignore default configuration module.',
231+
default=True, action='store_false')
232+
add('-p', '--python-tree', dest='pytree',
233+
help='Print python object tree to stderr.',
234+
default=False, action='store_true')
235+
add('-r', '--no-color', dest='nocolor',
236+
help='Disable color output.' +\
237+
(' No effect on Win OS.' if isWindows() else ''),
238+
default=False, action='store_true')
239+
add('-s', '--skip-source', dest='skipsource',
240+
help='Skip writing translated source; useful when printing trees',
241+
default=False, action='store_true')
242+
add('-t', '--lexer-tokens', dest='lexertokens',
243+
help='Print lexer tokens to stderr.',
244+
default=False, action='store_true')
245+
add('-v', '--version', action='version', version='%(prog)s ' + version)
246+
247+
ns = parser.parse_args(argv)
248+
if ns.inputfile == '-':
249+
ns.inputfile = sys.stdin
250+
if ns.outputfile == '-':
251+
ns.outputfile = sys.stdout
252+
253+
configColors(ns.nocolor)
254+
configLogging(ns.loglevel)
255+
return ns
256+
257+
258+
def main():
259+
runMain(configScript(sys.argv[1:]))
260+
261+
262+
if __name__ == '__main__':
263+
sys.exit(main())

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
1010
This version requires Python 2.7.
1111
"""
12-
13-
from distutils.core import setup
12+
from setuptools import find_packages, setup
13+
# from distutils.core import setup
1414
from os import path, listdir
1515

1616

@@ -72,7 +72,7 @@ def doc_files():
7272
'*.tokens',
7373
]
7474
},
75-
75+
entry_points={'console_scripts':['j2py = java2python.main:main']},
7676
scripts=[
7777
'bin/j2py',
7878
],

0 commit comments

Comments
 (0)