diff --git a/java2python/config/default.py b/java2python/config/default.py index ea34f46..a2cdb67 100644 --- a/java2python/config/default.py +++ b/java2python/config/default.py @@ -162,6 +162,12 @@ (Type('METHOD_CALL') > Type('DOT') > Type('IDENT', 'length'), transform.lengthToLen), + (Type('METHOD_CALL') > Type('DOT') > ( + Type('IDENT', 'String') + + Type('IDENT', 'format') + ), + transform.formatString), + (Type('TYPE') > Type('QUALIFIED_TYPE_IDENT') > Type('IDENT'), transform.typeSub), @@ -189,7 +195,6 @@ moduleOutputSubs = [ (r'System\.out\.println\((.*)\)', r'print \1'), (r'System\.out\.print_\((.*?)\)', r'print \1,'), - (r'String\.format\(\"(.*)\" *, *(.*)\)', r'"\1" % (\2)'), (r'(.*?)\.equals\((.*?)\)', r'\1 == \2'), (r'(.*?)\.equalsIgnoreCase\((.*?)\)', r'\1.lower() == \2.lower()'), (r'([\w.]+)\.size\(\)', r'len(\1)'), diff --git a/java2python/mod/transform.py b/java2python/mod/transform.py index 04fc3f5..55b49cd 100644 --- a/java2python/mod/transform.py +++ b/java2python/mod/transform.py @@ -11,6 +11,9 @@ # See the java2python.config.default and java2python.lang.selector modules to # understand how and when selectors are associated with these callables. +import re +from logging import warn + import keyword import types @@ -89,6 +92,65 @@ def lengthToLen(node, config): expr.addChild(ident) +def formatSyntaxTransf(match): + """ Helper function for formatString AST transform. + + Translates the Java Formatter syntax into Python .format syntax. + + This function gets called by re.sub which matches all the %...$... groups + inside a format specifier string. + """ + groups = match.groupdict() + result = '{' + # TODO: add flags, width and precision + if(groups['idx']): + idx = int(groups['idx'][:-1]) + result += str(idx - 1) # Py starts count from 0 + result += ':' + groups['convers'] + '}' + + return result + +def formatString(node, config): + """ Transforms string formatting like 'String.format("%d %2$s", i, s)' + into '"{:d} {2:s}".format(i, s)'. + """ + dot = node.parent + method = dot.parent + arg_list = method.firstChildOfType(tokens.ARGUMENT_LIST) + call_args = [arg for arg in arg_list.childrenOfType(tokens.EXPR)] + args = [arg.firstChildOfType(tokens.IDENT) for arg in call_args[1:]] + + # Translate format syntax (if format == string_literal) + format = call_args[0].firstChildOfType(tokens.STRING_LITERAL) + if format: + format.token.text = \ + re.sub(r'%(?P\d+\$)?(?P[scdoxefg])', + formatSyntaxTransf, + format.token.text, + flags=re.IGNORECASE) + else: + # Translation should happen at runtime + format = call_args[0].firstChild() + if format.type == tokens.IDENT: + # String variable + warn('Formatting string %s is not automatically translated.' + % str(format.token.text)) + else: + # Function that returns String + warn('Formatting string returned by %s() is not automatically translated.' + % str(format.firstChildOfType(tokens.IDENT).token.text)) + + left_ident = dot.children[0] + right_ident = dot.children[1] + + # Change AST + arg_list.children.remove(format.parent) + dot.children.remove(left_ident) + dot.children.remove(right_ident) + dot.addChild(format) + dot.addChild(right_ident) + + def typeSub(node, config): """ Maps specific, well-known Java types to their Python counterparts. diff --git a/test/Format0.java b/test/Format0.java index 5ac5321..995aad2 100644 --- a/test/Format0.java +++ b/test/Format0.java @@ -1,8 +1,8 @@ -public class Format { +public class Format0 { public static void main(String[] args) { int i = 22; String s = "text"; - String r = String.format("> (%d) %s", i, s); + String r = String.format("> (%1$d) %2$s", i, s); System.out.println(r); }