Last active
September 30, 2025 09:22
-
-
Save sma/8180927 to your computer and use it in GitHub Desktop.
Revisions
-
sma revised this gist
Jan 3, 2014 . 2 changed files with 29 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,7 +4,7 @@ This is an ad-hoc Java-to-Dart translator originally written on two (admittedly See <http://sma.github.io/stuff/java2dartweb/java2dartweb.html> for a demo. *Note*: It doesn't support the complete Java grammar specification and cannot translate everything. It only translates syntax and does not attempt to translate Java library classes and methods to Dart equivalents (with the exception of `String.charAt` and `StringBuffer.append`). You will have to make changes to the resulting Dart code. It does not support anonymous inner classes. However, I was able to successfully convert a 7000+ line command line application with only minimal fixes in 30 minutes. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -8,6 +8,8 @@ class Scanner { /// Holds the current token or the empty string on end of input String currentToken; int position; Iterator<Match> _matches; /// Constructs a new scanner to tokenize [source]. @@ -16,7 +18,7 @@ class Scanner { '\\s+|//.*\$|/\\*[\\s\\S]*?\\*/|' // whitespace & comments '(0x[0-9a-fA-F]+|' // numbers '(?:\\d+(?:\\.\\d*)?|\\.\\d+)' // numbers '(?:[eE][-+]?\\d+)?[lLfF]?|' // numbers '[\\w\$_]+|' // names & keywords '"(?:\\\\.|[^"])*?"|\'(?:\\\\.|[^\'])+?\'|' // strings & characters '&&|\\|\\||\\+\\+|--|' // operators @@ -32,6 +34,8 @@ class Scanner { while (_matches.moveNext()) { Match m = _matches.current; if (m[1] != null) { position = m.input.substring(0, m.start).split("\n").length; currentToken = m[1]; return token; } @@ -696,6 +700,17 @@ class Parser extends Scanner { var type = parseType(); if (at("[")) { while (at("]")) { if (at("{")) { var list = []; while (!at("}")) { list.add(parseExpression()); if (at("}")) { break; } expect(","); } return ["new_array_from", type, list]; } type = ["array_type", type]; expect("["); } @@ -723,7 +738,11 @@ class Parser extends Scanner { return ["literal", advance()]; } if (isNumber) { var n = advance(); if (n.contains(new RegExp("[lLfF]\$"))) { n = n.substring(0, n.length - 1); } return ["literal", num.parse(n)]; } // need to distinguish type declarations and variables // a sequence of two names should be a type declaration @@ -1010,6 +1029,9 @@ class Translator { "new_array": (node) { return "new List<${translate(node[1])}>(${translate(node[2])})"; }, "new_array_from": (node) { return "new List<${translate(node[1])}>.from([${node[2].map(translate).map(_strip).join(", ")}])"; }, "new_instance": (node) { return "new ${translate(node[1])}(${node[2].map(translate).map(_strip).join(", ")})"; }, @@ -1038,6 +1060,8 @@ class Translator { "-=": _binaryExpression("-="), "*=": _binaryExpression("*="), "/=": _binaryExpression("~/="), // int only "&=": _binaryExpression("&="), "|=": _binaryExpression("|="), "[]": (node) { return "${translate(node[1])}[${_strip(translate(node[2]))}]"; }, "p++": (node) { return "${translate(node[1])}++"; }, "p--": (node) { return "${translate(node[1])}--"; }, @@ -1174,6 +1198,8 @@ class Translator { "throw": (node) { emit("throw ${translate(node[1])};"); }, "pass": (node) { }, }; _resolver.beginScope(); } -
sma revised this gist
Jan 1, 2014 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,6 +2,8 @@ This is an ad-hoc Java-to-Dart translator originally written on two (admittedly long) evenings. See <http://sma.github.io/stuff/java2dartweb/java2dartweb.html> for a demo. *Note*: It doesn't support the complete Java grammar specification and cannot translate everything. It only translates syntax and does not attempt to translate Java library classes and methods to Dart equivalents (with the exception of `String.charAt` and `StringBuffer.append`). You will have to make changes to the resulting Dart code. However, I was able to successfully convert a 7000+ line command line application with only minimal fixes in 30 minutes. -
sma revised this gist
Jan 1, 2014 . 3 changed files with 590 additions and 273 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,14 +1,14 @@ # Java to Dart This is an ad-hoc Java-to-Dart translator originally written on two (admittedly long) evenings. *Note*: It doesn't support the complete Java grammar specification and cannot translate everything. It only translates syntax and does not attempt to translate Java library classes and methods to Dart equivalents (with the exception of `String.charAt` and `StringBuffer.append`). You will have to make changes to the resulting Dart code. However, I was able to successfully convert a 7000+ line command line application with only minimal fixes in 30 minutes. Because Dart doesn't support overloading methods, I strongly recommend to first rename (using your favorite IDE) those methods in Java. I also noticed that Dart doesn't like if types, fields, or methods have the same name. Again, I recommend to rename all such occurrences before translating. My biggest problem was that there is no `character` type. I tried to fix that by adding `codeUnitAt` to all character constants and writing my own little `Character` class but the result is quite ugly. My code (being quite old) didn't use Java collections. I recommend to create your own compatibility implementations (see `java.dart`). I like using (rather complex) regular expressions to quickly build scanners. It seems that Dart's regular expression engine is quite slow. It takes more than 20 seconds to parse the Java code. That caught me by suprise. Because I couldn't stand waiting half a minute every time I ran the application to incrementally build the translator, I came up with a two pass approach. First, I generate an AST and save it as JSON document. Then, I read that file again and translate it. Just reading the JSON is very fast. To have the AST easily serializable, I opted for a Lisp-like all-list approach - in case you wonder why I didn't use classes for AST nodes. @@ -18,9 +18,9 @@ The `Scanner` breaks a source string into tokens. It knows the `currentToken`. A The `Parser` inherits (to get access to the scanner's methods without the need of an explicit receiver) from `Scanner` and provides a hand-crafted LL(1) recursive decent parser. I like to build those. The start rule is `parseCompilationUnit()`. I constructed most rules from memory and only consulted a precedence table for all those operators. There might be errors, therefore. I know that I didn't cover annotations and some esotheric generics syntax. All `parseXXX()` methods are supposed to return an AST represented by a `List` consisting of strings and more lists. The first element always contains a string with the node type. This type is used by the `Translator` to dispatch. I started to use constants for AST types, but I didn't finished that transformation. I also probably should have added more comments to the parser. I feel bad. The method-flow follows an imaginary EBNF grammar of Java version 5 or 6. The `Translator` takes the AST and writes the translated Dart program to a `StringSink` which defaults to `stdout`. Entry method and main dispatch is `translate()`. It uses a `Map` from AST types to functions accepting a single node and processing that node, recursively calling `translate()` if needed. The translator provides `emit()` to write an indented line and `indent()` and `dedent()` to change the indentation. There's also a `newline()` method. The translator uses a dead-simple `Resolver` to keep track of variables types which is then used to detect `StringBuffer.append` calls to rewrite them as `write` calls. And that's all there is. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,156 @@ // Copyright 2014 Stefan Matthias Aust. Licensed under http://opensource.org/licenses/MIT. import 'dart:io' as dart_io; // collections ------------------------------------------------------------------------------------ abstract class java_util_List<E> { int size(); bool add(E e); bool remove(Object o); void clear(); java_util_Iterator<E> iterator(); } class java_util_ArrayList<E> extends java_util_List<E> { List<E> _elements; java_util_ArrayList([arg]) { if (arg == null || arg is num) { _elements = []; } else if (arg is java_util_ArrayList) { _elements = arg._elements; } else throw "unsupported argument type"; } int size() => _elements.length; bool add(E e) { _elements.add(e); return true; } bool remove(Object o) => _elements.remove(o); void clear() => _elements.clear(); java_util_Iterator<E> iterator() => new java_util_Iterator.dart(_elements.iterator); } class java_util_Iterator<E> { final Iterator<E> _iterator; bool _hasNext; java_util_Iterator.dart(this._iterator) { _hasNext = _iterator.moveNext(); } bool hasNext() => _hasNext; E next() { E e = _iterator.current; _hasNext = _iterator.moveNext(); return e; } void remove() { throw "unsupported operation exception"; } } // core ------------------------------------------------------------------------------------------- class Integer { static final int MAX_VALUE = 2147483647, MIN_VALUE = -2147483648; } class Character { static bool isWhitespace(int ch) { return new String.fromCharCode(ch).startsWith(new RegExp("\\s")); } static int toLowerCase(int ch) { return new String.fromCharCode(ch).toLowerCase().codeUnitAt(0); } } bool java_equalsIgnoreCase(String s1, String s2) { return s1 == s2 || s1.toLowerCase() == s2.toLowerCase(); } // io --------------------------------------------------------------------------------------------- abstract class java_io_InputStream { void close(); int read(); } abstract class java_io_OutputStream { void close(); void flush(); void write(obj); } class _OutStream extends java_io_OutputStream { void close() {} void flush() {} void write(obj) { if (obj is int) { dart_io.stdout.writeCharCode(obj); } else if (obj is List<int>){ dart_io.stdout.add(obj); } else throw "unsupported argument type"; } void print(String s) => dart_io.stdout.write(s); void println([String s = ""]) => dart_io.stdout.writeln(s); } class _InStream extends java_io_InputStream { void close() {} int read() => dart_io.stdin.readByteSync(); } class System { static final out = new _OutStream(); static final in_ = new _InStream(); static int currentTimeMillis() => new DateTime.now().millisecondsSinceEpoch; static void exit(int code) => dart_io.exit(code); static void arraycopy(List src, int si, List dst, int di, int n) { dst.setRange(di, di + n, src.sublist(si)); } } class java_io_IOException implements Exception { final String message; java_io_IOException(this.message); void printStackTrace() { print("Exception: $message"); } } class java_io_FileNotFoundException extends java_io_IOException { java_io_FileNotFoundException(String message) : super(message); } class java_io_File { final String _name; java_io_File(this._name); bool delete() { new dart_io.File(_name).deleteSync(); return true; } bool mkdir() { new dart_io.Directory(_name).createSync(); return true; } List<java_io_File> listFiles() => new dart_io.Directory(_name).listSync().map((e) => new java_io_File(e.path)); String getAbsolutePath() => new dart_io.File(_name).absolute.path; String getName() => _name; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,28 +1,32 @@ // Copyright 2014 Stefan Matthias Aust. Licensed under http://opensource.org/licenses/MIT. library java2dart; import 'dart:io'; import 'dart:convert'; class Scanner { /// Holds the current token or the empty string on end of input String currentToken; Iterator<Match> _matches; /// Constructs a new scanner to tokenize [source]. Scanner(String source) { _matches = new RegExp( '\\s+|//.*\$|/\\*[\\s\\S]*?\\*/|' // whitespace & comments '(0x[0-9a-fA-F]+|' // numbers '(?:\\d+(?:\\.\\d*)?|\\.\\d+)' // numbers '(?:[eE][-+]?\\d+)?|' // numbers '[\\w\$_]+|' // names & keywords '"(?:\\\\.|[^"])*?"|\'(?:\\\\.|[^\'])+?\'|' // strings & characters '&&|\\|\\||\\+\\+|--|' // operators '[+\\-*/%&^|]=?|<<=?|>>>?=?|[=<>!]=?|~|' // operators '[.]{3}|[.,;()[\\]{}?:])|(.)', // syntax multiLine: true).allMatches(source).iterator; advance(); } /// Advances [currentToken] to the next token in the source. String advance() { String token = currentToken; while (_matches.moveNext()) { @@ -63,32 +67,62 @@ class Scanner { bool get isNumber => currentToken.startsWith(new RegExp("\\.?[\\d]")); /// Returns true if [currentToken] is a string. bool get isString => currentToken.isNotEmpty && (currentToken[0] == '"' || currentToken[0] == "'"); void error(String message) { throw new Exception(message); } } class Parser extends Scanner { /// Constructs a new parser to parse [source]. Parser(String source) : super(source); /// Returns [node] after parsing a semicolon. andSemicolon(node) { expect(";"); return node; } /// Returns the result of calling [parser] if [at(token)] is true and null otherwise. parseIfAt(String token, parser()) => at(token) ? parser() : null; /// Returns a list of results from calling [parser] as long as [at(token)] is true. List parseWhile(String token, parser()) { var results = []; while (at(token)) { results.add(parser()); } return results; } /// Returns a list of results from calling [parser] as long as [at(token)] is false. List parseWhileNot(token, parser()) { var results = []; while (!at(token)) { results.add(parser()); } return results; } /// Returns a list of results from calling [parser], separated by [separator]. List parseList(parser(), {String ifAt, String separator: ","}) { if (ifAt == null || at(ifAt)) { var results = [parser()]; while (at(separator)) { results.add(parser()); } return results; } else { return null; } } // declarations --------------------------------------------------------------------------------- // ["package" qualifiedName ";"] { importStatement } { typeDeclaration } parseCompilationUnit() { String packageName = parseIfAt("package", () => andSemicolon(parseQualifiedName())); var imports = parseWhile("import", parseImportStatement); var declarations = parseWhileNot("", parseTypeDeclaration); return [AST.CompilationUnit, packageName, imports, declarations]; } // "import" name {"." name} ["." "*"] ";" @@ -99,7 +133,7 @@ class Parser extends Scanner { names.add(at("*") ? "*" : parseName()); } expect(";"); return [AST.Import, names.join(".")]; } // classDeclaration | interfaceDeclaration @@ -111,107 +145,84 @@ class Parser extends Scanner { error("expected class or interface but found $currentToken."); } static const modifierTokens = const["abstract", "public", "protected", "private", "static", "final", "synchronized"]; parseModifiers() { var modifiers = []; while (modifierTokens.contains(currentToken)) { modifiers.add(advance()); } return [AST.Modifiers, modifiers]; } // [modifiers] [typeParameter] "class" name ["extends" type] ["implements" type {"," type] classBody parseClassDeclaration(modifiers) { var className = parseName(); var typeParameters = parseIfAt("<", parseTypeParameters); var superclassType = parseIfAt("extends", parseType); var interfaceTypes = parseList(parseType, ifAt:"implements"); return [AST.Class, modifiers, className, typeParameters, superclassType, interfaceTypes, parseClassOrInterfaceBody()]; } // [modifiers] [typeParameter] "interface" name ["extends" type {"," type}] interfaceBody parseInterfaceDeclaration(modifiers) { var interfaceName = parseName(); var typeParameters = parseIfAt("<", parseTypeParameters); var interfaceTypes = parseList(parseType, ifAt:"extends"); return [AST.Interface, modifiers, interfaceName, typeParameters, interfaceTypes, parseClassOrInterfaceBody()]; } // "{" {memberDeclaration} "}" // should distinguish class & interface (has no static, no method bodies) parseClassOrInterfaceBody() { expect("{"); return parseWhileNot("}", parseMemberDeclaration); } parseMemberDeclaration() { parseIfAt("<", parseTypeParameters); // ignored var modifiers = parseModifiers(); if (modifiers[1].length == 1 && modifiers[1][0] == "static" && at("{")) { return [AST.StaticInitializer, parseStatementBlock()]; } if (at("class")) return parseClassDeclaration(modifiers); if (at("interface")) return parseInterfaceDeclaration(modifiers); String name = parseName(); // could be a type or constructor if (at("(")) { // it's a constructor declaration var parameters = parseParameterList(); var throws = parseThrowsDeclaration(); expect("{"); return [AST.Constructor, modifiers, name, parameters, throws, parseStatementBlock()]; } var type = parseArrayType(parseType(name)); name = parseName(); if (at("(")) { // it's a method declaration var parameters = parseParameterList(); type = parseArrayType(type); var throws = parseThrowsDeclaration(); var statements; if (!at(";")) { expect("{"); statements = parseStatementBlock(); } else { statements = []; } return [AST.Method, modifiers, type, name, parameters, throws, statements]; } else { // must be variable declaration var declarations = parseVariableDeclarations(modifiers, type, name); expect(";"); return declarations; } } List parseThrowsDeclaration() { return parseList(parseType, ifAt: "throws"); } List parseParameterList() { var parameters = []; while (!at(")")) { parameters.add(parseParameter()); if (at(")")) break; expect(","); } @@ -224,7 +235,31 @@ class Parser extends Scanner { bool rest = at("..."); var name = parseName(); type = parseArrayType(type); return [rest ? AST.RestParameter : AST.Parameter, type, name]; } parseVariableDeclarations(modifiers, type, name) { var type2 = parseArrayType(type); var init = parseInitializer(); var declarations = [[AST.VariableDeclaration, modifiers, type2, name, init]]; while (at(",")) { name = parseName(); type2 = parseArrayType(type); init = parseInitializer(); declarations.add([AST.VariableDeclaration, modifiers, type2, name, init]); } return [AST.VariableDeclarations, declarations]; } parseInitializer() { if (at("=")) { if (at("{")) { return parseArrayInitializer(); } else { return parseExpression(); } } return null; } parseArrayInitializer() { @@ -238,46 +273,40 @@ class Parser extends Scanner { if (at("}")) break; expect(","); } return [AST.ArrayInitializer, elements]; } // types ---------------------------------------------------------------------------------------- static const primitiveTypes = const["boolean", "byte", "char", "short", "int", "float", "long", "double", "void"]; parseType([String name=""]) { if (name == "") { name = advance(); } var type = parseSimpleType(name); if (at("<")) { type = [AST.GenericType, type, parseTypeParameters()]; } return type; } parseSimpleType(String name) { if (primitiveTypes.contains(name)) { return [AST.PrimitiveType, name]; } else { var names = [name]; while (at(".")) { names.add(parseName()); } return [AST.ReferenceType, names.join(".")]; } } parseArrayType(type) { while (at("[")) { expect("]"); type = [AST.ArrayType, type]; } return type; } @@ -288,46 +317,17 @@ class Parser extends Scanner { parameters.add(parseTypeParameter()); } expect(">"); return [AST.TypeParameters, parameters]; } String parseTypeParameter() { return at("?") ? "?" : parseQualifiedName(); } // statements ----------------------------------------------------------------------------------- parseStatementBlock() { return [AST.Block, parseWhileNot("}", parseStatement)]; } parseStatement() { @@ -343,37 +343,32 @@ class Parser extends Scanner { return ["synchronized", expression, parseStatement()]; } if (at("return")) { var expression; if (!at(";")) { expression = andSemicolon(parseExpression()); } return ["return", expression]; } if (at("throw")) { return ["throw", andSemicolon(parseExpression())]; } if (at("assert")) { var e = parseExpression(); var m = parseIfAt(":", parseExpression); expect(";"); return ["assert", e, m]; } if (at("break")) { var label; if (!at(";")) { label = andSemicolon(parseName()); } return ["break", label]; } if (at("continue")) { var label; if (!at(";")) { label = andSemicolon(parseName()); } return ["continue", label]; } @@ -386,21 +381,20 @@ class Parser extends Scanner { if (expression[0] == "variable_declarations") { return expression; } return ["expression", expression]; } parseIfStatement() { var c = parseParenthesizedExpression(); var t = parseStatement(); var e = parseIfAt("else", parseStatement); return ["if", c, t, e]; } parseDoWhileStatement() { var s = parseStatement(); expect("while"); var e = andSemicolon(parseParenthesizedExpression()); return ["dowhile", e, s]; } @@ -430,8 +424,7 @@ class Parser extends Scanner { expect(";"); } if (!at(";")) { b = andSemicolon(parseExpression()); } if (!at(")")) { c.add(parseExpression()); @@ -453,7 +446,7 @@ class Parser extends Scanner { expect(")"); c.add(["catch", p, parseStatement()]); } var f = parseIfAt("finally", parseStatement); return ["try", s, c, f]; } @@ -561,12 +554,17 @@ class Parser extends Scanner { var e = parseBitShiftExpression(); while (true) { if (at("<")) { // hack: could be a variable declaration (still missing "?" case) var e2 = parseBitShiftExpression(); if (e[0] == "variable" && e2[0] == "variable" && (currentToken == "," || currentToken == ">")) { var parameters = [e2[1]]; while (!at(">")) { expect(","); parameters.add(parseQualifiedName()); } var type = parseArrayType(["generic_type", parseSimpleType(e[1]), ["type_parameters", parameters]]); var name = parseName(); return parseVariableDeclarations(null, type, name); } e = ["<", e, e2]; } else if (at("<=")) { @@ -660,18 +658,19 @@ class Parser extends Scanner { } else if (at("[")) { // hack: could be a variable declaration if (e[0] == "variable" && at("]")) { var type = ["array_type", parseSimpleType(e[1])]; return parseVariableDeclarations(null, parseArrayType(type), parseName()); } var index = parseExpression(); expect("]"); e = ["[]", e, index]; } else if (at(".")) { // method call or field access var name = parseName(); if (at("(")) { // method call e = ["call", e, name, parseArguments()]; } else { e = ["field", e, name]; } } else return e; } } @@ -686,14 +685,13 @@ class Parser extends Scanner { return arguments; } // needed to distinguish casts from parenthesized expressions static const literalFirst = const["++", "+", "--", "-", "!", "~", "("]; parseLiteral() { if (at("null")) return ["literal", null]; if (at("true")) return ["literal", true]; if (at("false")) return ["literal", false]; if (at("new")) { var type = parseType(); if (at("[")) { @@ -729,12 +727,12 @@ class Parser extends Scanner { } // need to distinguish type declarations and variables // a sequence of two names should be a type declaration // ("foo[] bar" or "foo<x> bar" is not detected, see "hack") if (isName) { var name = advance(); if (isName) { var type = parseArrayType(parseType(name)); return parseVariableDeclarations(null, type, parseName()); } return ["variable", name]; } @@ -743,20 +741,8 @@ class Parser extends Scanner { // utilities ------------------------------------------------------------------------------------------------------- String parseQualifiedName() { return parseList(parseName, separator: ".").join("."); } String parseName() { @@ -767,15 +753,83 @@ class Parser extends Scanner { } } abstract class AST { // declarations static const CompilationUnit = "unit"; /* String packageName, List<AST> imports, List<AST> declarations */ static const Import = "import"; /* String qualifiedName */ static const Modifiers = "modifiers"; /* List<String> modifiers */ static const Class = "class"; /* AST modifiers, String className, AST typeParameters, AST superclassType, List<AST> interfaceTypes, List<AST> classBody */ static const Interface = "interface"; /* AST modifiers, String interfaceName, AST typeParameters, List<AST> interfaceTypes, List<AST> classBody */ static const StaticInitializer = "static_initializer"; /* AST block */ static const Constructor = "constructor"; /* AST modifiers, String constructorName, List<AST> parameters, List<AST> throws, AST block */ static const Method = "method"; /* AST modifiers, AST returnType, String methodName, List<AST> parameters, List<AST> throws, AST block */ static const Parameter = "parameter"; /* AST type, AST name */ static const RestParameter = "rest_parameter"; /* AST type, AST name */ static const VariableDeclaration = "variable_declaration"; /* AST modifiers, AST type, String name, AST initializer */ static const VariableDeclarations = "variable_declarations"; /* List<AST> declarations */ static const ArrayInitializer = "array"; /* List<AST> elements */ // types static const GenericType = "generic_type"; /* AST type, AST parameters */ static const PrimitiveType = "primitive_type"; /* String name */ static const ReferenceType = "reference_type"; /* String qualifiedName */ static const ArrayType = "array_type"; /* AST type */ static const TypeParameters = "type_parameters"; /* List<String> parameters */ // statements static const Block = "block"; /* List<AST> statements */ static const Assert = "assert"; static const Break = "break"; static const Continue = "continue"; static const DoWhile = "dowhile"; static const Expression = "expression"; /* AST expression */ static const For = "for"; static const ForEach = "foreach"; static const If = "if"; static const Label = "label"; static const Pass = "pass"; static const Return = "return"; static const Throw = "throw"; static const While = "whiledo"; static const Try = "try"; static const Catch = "catch"; static const Finally = "finally"; static const Switch = "switch"; static const Case = "case"; static const Default = "default"; // expressions static const Assign = "="; static const PlusAssign = "+="; static const MinusAssign = "-="; static const MultiplyAssign = "*="; static const DivideAssign = "/="; static const ModuloAssign = "%="; static const AndAssign = "&="; static const IorAssign = "|="; static const XorAssign = "^="; static const LeftShiftAssign = "<<="; static const RightShiftAssign = ">>="; static const URightShiftAssign = ">>>="; static const Conditional = "?:"; static const Or = "||"; static const And = "&&"; // ... } /// Translates Java nodes into Dart (printed to [stdout]). class Translator { StringSink _sink; Map _funcs; int _indent = 0; Map _imports = new Map(); Set _types = new Set(); Resolver _resolver = new Resolver(); Translator([this._sink]) { if (_sink == null) _sink = stdout; _funcs = { AST.CompilationUnit: (node) { if (node[1] != null) { emit("library ${node[1]};"); if (node[2].isNotEmpty) { @@ -785,83 +839,161 @@ class Translator { node[2].forEach(translate); node[3].forEach(translate); }, AST.Import: (node) { // import a compatibility file instead if (_imports.isEmpty) { emit("import 'java.dart';"); } // to replace simple names with qualified names _imports[node[1].split(".").last] = node[1].replaceAll(".", "_"); }, AST.Class: (node) { newline(); var abst = node[1][1].contains("abstract") ? "abstract " : ""; var name = node[2]; var type = node[3] != null ? translate(node[3]) : ""; var ext = node[4] != null ? " extends ${translate(node[4]).replaceAll(".", "_")}" : ""; var imp = node[5] != null ? " with ${node[5].map(translate).join(", ").replaceAll(".", "_")}" : ""; emit("${abst}class $name$type$ext$imp"); _resolver.beginScope(); emit("{"); indent(); node[6].where((n) => n[0] != "class").forEach(translate); // See below dedent(); emit("}"); _resolver.endScope(); // Dart doesn't support nested classes node[6].where((n) => n[0] == "class").forEach(translate); }, AST.Interface: (node) { newline(); var name = node[2]; var type = node[3] != null ? translate(node[3]) : ""; var imp = node[4] != null ? " with ${node[4].map(translate).join(", ").replaceAll(".", "_")}" : ""; emit("abstract class $name$type$imp"); _resolver.beginScope(); emit("{"); indent(); node[5].where((n) => n[0] != "class").forEach(translate); // See below dedent(); emit("}"); _resolver.endScope(); // Dart doesn't support nested classes node[5].where((n) => n[0] == "class").forEach(translate); }, AST.VariableDeclarations: (node) { node[1].forEach((n) => emit(translate(n) + ";")); }, AST.VariableDeclaration: (node) { var mods = node[1] != null ? translate(node[1]) : ""; var type = translate(node[2]); if (type == "int") mods = mods.replaceAll("final", "const"); var name = _dartName(node[3]); var init = node[4] != null ? " = ${translate(node[4])}" : ""; _resolver.bind(name, type); return "$mods$type $name$init"; }, AST.ArrayInitializer: (node) { return "[${node[1].map(translate).map(_strip).join(", ")}]"; }, AST.Modifiers: (node) { var mods = ""; if (node[1].contains("static")) mods += "static "; if (node[1].contains("final")) mods += "final "; return mods; }, AST.Constructor: (node) { _resolver.beginScope(); newline(); var mods = node[1] != null ? translate(node[1]) : ""; var name = _dartName(node[2]); emit("$mods$name(${node[3].map(translate).join(", ")})"); emit("{"); indent(); translate(node[5]); dedent(); emit("}"); _resolver.endScope(); }, AST.Method: (node) { _resolver.beginScope(); newline(); var mods = node[1] != null ? translate(node[1]) : ""; var type = translate(node[2]); var name = _dartName(node[3]); emit("$mods$type $name(${node[4].map(translate).join(", ")})${node[6].isEmpty ? ";": ""}"); if (node[6].isNotEmpty) { emit("{"); indent(); translate(node[6]); dedent(); emit("}"); } _resolver.endScope(); }, AST.Parameter: (node) { var type = translate(node[1]), name = _dartName(node[2]); _resolver.bind(name, type); return "$type $name"; }, AST.RestParameter: (node) { var type = translate(node[1]), name = _dartName(node[2]); _resolver.bind(name, type); return "List<$type> $name /*XXX*/"; }, AST.Block: (node) { _resolver.beginScope(); node[1].forEach(translate); _resolver.endScope(); }, AST.PrimitiveType: (node) { return _dartType(node[1]); }, AST.ReferenceType: (node) { _types.add(node[1]); // TODO should only collect self-defined types var imported = _imports[node[1]]; return imported != null ? imported : node[1]; }, AST.ArrayType: (node) { return "List<${translate(node[1])}>"; }, AST.GenericType: (node) { var type = translate(node[1]); if (node[2] != null && node[2][1][0] != "?") { type = "$type${translate(node[2])}"; } return type; }, AST.TypeParameters: (node) { return "<${node[1].join(", ")}>"; }, AST.Expression: (node) { emit("${_strip(translate(node[1]))};"); }, "call": (node) { var receiver = node[1] != null ? translate(node[1]) : ""; if (node[1] != null && node[2] == "equals" && node[3].length == 1) { return "($receiver == ${translate(node[3][0])})"; } if (node[1] != null && node[2] == "length" && node[3].length == 0) { return "$receiver.length"; } if (node[1] != null && node[2] == "charAt" && node[3].length == 1) { return "$receiver.codeUnitAt(${_strip(translate(node[3][0]))})"; } if (node[1] != null && node[2] == "equalsIgnoreCase" && node[3].length == 1) { return "java_equalsIgnoreCase($receiver, ${_strip(translate(node[3][0]))})"; } if (node[1] != null && node[2] == "append" && node[3].length == 1) { if (_resolver.resolve(node[1]) == "StringBuffer") { node[2] = "write"; } } if (receiver != "") receiver += "."; return "$receiver${_dartName(node[2])}(${node[3].map(translate).map(_strip).join(", ")})"; }, "field": (node) { return "${translate(node[1])}.${_dartName(node[2])}"; }, "literal": (node) { if (node[1] is String) { @@ -873,66 +1005,57 @@ class Translator { return "${node[1]}"; }, "variable": (node) { return _dartName(node[1]); }, "new_array": (node) { return "new List<${translate(node[1])}>(${translate(node[2])})"; }, "new_instance": (node) { return "new ${translate(node[1])}(${node[2].map(translate).map(_strip).join(", ")})"; }, // TODO use precedence to omit unneeded parentheses "+": _binaryExpression("+"), "-": _binaryExpression("-"), "*": _binaryExpression("*"), "/": _binaryExpression("~/"), // int only "%": _binaryExpression("%"), "&": _binaryExpression("&"), "|": _binaryExpression("|"), "^": _binaryExpression("^"), "<<": _binaryExpression("<<"), ">>": _binaryExpression(">>"), ">>>": _binaryExpression(">>"), "&&": _binaryExpression("&&"), "||": _binaryExpression("||"), "<": _binaryExpression("<"), "<=": _binaryExpression("<="), ">": _binaryExpression(">"), ">=": _binaryExpression(">="), "==": _binaryExpression("=="), "!=": _binaryExpression("!="), "=": _binaryExpression("="), "+=": _binaryExpression("+="), "-=": _binaryExpression("-="), "*=": _binaryExpression("*="), "/=": _binaryExpression("~/="), // int only "[]": (node) { return "${translate(node[1])}[${_strip(translate(node[2]))}]"; }, "p++": (node) { return "${translate(node[1])}++"; }, "p--": (node) { return "${translate(node[1])}--"; }, "++p": (node) { return "(++${translate(node[1])})"; }, "--p": (node) { return "(--${translate(node[1])})"; }, "!": (node) { return "(!${translate(node[1])})"; }, "~": (node) { return "(~${translate(node[1])})"; }, "-p": (node) { return "(-${translate(node[1])})"; }, "+p": (node) { return "(+${translate(node[1])})"; }, "cast": (node) { //return "(${translate(node[2])} as ${translate(node[1])})"; return translate(node[2]); }, "?:": (node) { return "(${translate(node[1])} ? ${translate(node[2])} : ${translate(node[3])})"; }, "if": (node) { emit("if (${_strip(translate(node[1]))}) {"); indent(); translate(node[2]); if (node[3] != null) { @@ -946,7 +1069,7 @@ class Translator { }, "return": (node) { if (node[1] != null) { emit("return ${_strip(translate(node[1]))};"); } else { emit("return;"); } @@ -959,11 +1082,11 @@ class Translator { }); if (i.length > 1) { // Dart doesn't like multiple declarations in for so we move them i.forEach((n) => emit("${translate(n)};")); i = []; } emit("for (${i.map(translate).join(", ")}; " "${node[2] != null ? _strip(translate(node[2])) : ""}; " "${node[3].map(translate).join(", ")}) {"); indent(); translate(node[4]); @@ -982,10 +1105,10 @@ class Translator { indent(); translate(node[2]); dedent(); emit("} while (${_strip(translate(node[1]))});"); }, "whiledo": (node) { emit("while (${_strip(translate(node[1]))}) {"); indent(); translate(node[2]); dedent(); @@ -1006,13 +1129,13 @@ class Translator { emit("}"); }, "catch": (node) { emit("} on ${translate(node[1][1])} catch (${_dartName(node[1][2])}) {"); indent(); translate(node[2]); dedent(); }, "assert": (node) { emit("assert(${_strip(translate(node[1]))});"); }, "break": (node) { if (node[1] != null) { @@ -1052,9 +1175,14 @@ class Translator { emit("throw ${translate(node[1])};"); }, }; _resolver.beginScope(); } Function _binaryExpression(String op) { return (node) { return "(${translate(node[1])} $op ${translate(node[2])})"; }; } String _dartType(String type) { // map Java type to Dart type return const{ "boolean": "bool", @@ -1069,13 +1197,15 @@ class Translator { }[type]; } String _dartName(String name) { // do not use Dart reserved words if (const["in", "is"].contains(name)) name += "_"; // do not use the same name as for types if (_types.contains(name)) name += "_"; return name; } String _strip(String s) { // ignore parentheses on top level if (s.startsWith("(") && s.endsWith(")")) { return s.substring(1, s.length - 1); @@ -1084,14 +1214,14 @@ class Translator { } void newline() { _sink.write("\n"); } void emit(String line) { for (int i = 0; i < _indent; i++) { _sink.write(' '); } _sink.writeln(line); } void indent() { _indent++; } @@ -1103,6 +1233,36 @@ class Translator { } } class Resolver { List<Map<String, String>> _scopes = []; void beginScope() { _scopes.add({}); } void endScope() { _scopes.removeLast(); } void bind(String variable, String type) { _scopes.last[variable] = type; } String lookup(String variable) { for (int i = _scopes.length - 1; i >= 0; i--) { String type = _scopes[i][variable]; if (type != null) return type; } return null; } String resolve(node) { // we can resolve variables but nothing more if (node[0] == 'variable') return lookup(node[1]); return null; } } void main(List<String> args) { if (args.length > 0) { var source = new File("/Users/sma/Desktop/Atlantis.java").readAsStringSync(); @@ -1113,3 +1273,4 @@ void main(List<String> args) { new Translator().translate(node); } } -
sma created this gist
Dec 30, 2013 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,27 @@ # Java to Dart This is an ad-hoc Java-to-Dart Transpiler written on two (admittedly long) evenings. *Note*: It doesn't support the complete Java grammar specification and cannot translate everything. It only translates syntax and does not attempt to translate Java library classes and methods to Dart equivalents (with the exception of `String.charAt`). You will have to make changes to the resulting Dart code. However, I was able to successfully convert a 7000+ line command line application with only minimal fixes in 30 minutes. Because Dart doesn't support overloading methods, I strongly recommend to first rename (using your favorite IDE) those methods in Java. I also noticed that Dart doesn't like if types, fields, or methods have the same name. Again, I recommend to rename all such occurrences before translating. My biggest problem was that there is no `character` type. I tried to fix that by adding `codeUnitAt` everywhere and writing my own little `Character` class but the result is quite ugly. My code (being quite old) didn't use Java collections. They will probably cause problems because of similar named classes (`List`, `Iterator`) with different behavior. You may want to import and rename Dart core classes with some prefix. I like using (rather complex) regular expressions to quickly build scanners. It seems that Dart's regular expression engine is quite slow. It takes more than 20 seconds to parse the Java code. That caught me by suprise. Because I couldn't stand waiting half a minute every time I ran the application to incrementally build the translator, I came up with a two pass approach. First, I generate an AST and save it as JSON document. Then, I read that file again and translate it. Just reading the JSON is very fast. To have the AST easily serializable, I opted for a Lisp-like all-list approach - in case you wonder why I didn't use classes for AST nodes. ## How does it work? The `Scanner` breaks a source string into tokens. It knows the `currentToken`. All tokens are strings. Use `advance()` to get the next token. End of input is denoted by the empty token. The `at()` method will check for a given token and advances if found. This is the main driver for my hand-crafted LL(1) recursive decent parser. `expect()` will call `at()` and raises an error if the given token is not there. This checks for syntactic sugar like parentheses around `if` expressions. `error()` will raise an exception. The `Parser` inherits (to get access to the scanner's methods without the need of an explicit receiver) from `Scanner` and provides a hand-crafted LL(1) recursive decent parser. I like to build those. The start rule is `parseCompilationUnit()`. I constructed most rules from memory and only consulted a precedence table for all those operators. There might be errors, therefore. I know that I didn't cover annotations and some esotheric generics syntax. All `parseXXX()` methods are supposed to return an AST represented by a `List` consisting of strings and more lists. The first element always contains a string with the node type. This type is used by the `Translator` to dispatch. I probably should have used constants for the types to make sure that there's no hard to find spelling mistake but I didn't. I also probably should have added more comments. I feel bad. The method flow follows an imaginary EBNF grammar of Java version 5 or 6. The `Translator` takes the AST and prints the translated Dart program to `stdout`. Entry method and main dispatch is `translate()`. It uses a `Map` from AST types to functions accepting a single node and processing that node, recursively calling `translate()` if needed. I thought about using mirrors to automagically map types to methods but then I'd have to loose all those `[]` and `?:` types and I didn't want to make that change yet. The translator provides `emit()` to write an indented line and `indent()` and `dedent()` to change the indentation. There's also a `newline()` method. And that's all there is. Stefan This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,1115 @@ // Copyright 2013 Stefan Matthias Aust. Licensed under http://opensource.org/licenses/MIT. library java2dart; import 'dart:io'; import 'dart:convert'; class Scanner { String currentToken; Iterator<Match> _matches; Scanner(String source) { _matches = new RegExp( '\\s+|//.*\$|/\\*[\\s\\S]*?\\*/|' // whitespace & comments '(\\d+(?:\\.\\d*|\\.\\d+)(?:[eE][-+]\\d+)|' // numbers '[\\w\$_]+|' // names & keywords '"(?:\\\\.|[^"])*?"|\'(?:\\\\.|[^\'])+?\'|' // strings & characters '&&|\\|\\||\\+\\+|--|' // operators '[+\\-*/%&^|]=?|<<=?|>>>?=?|[=<>!]=?|~|' // operators '[.]{3}|[.,;()[\\]{}?:])|(.)', // syntax multiLine: true).allMatches(source).iterator; advance(); } /// Advances [currentToken] to the next token. String advance() { String token = currentToken; while (_matches.moveNext()) { Match m = _matches.current; if (m[1] != null) { currentToken = m[1]; return token; } if (m[2] != null) { error("unknown token ${m[2]} at ${m.start}"); } } currentToken = ""; return token; } /// Returns true if the current token matches [token] and false otherwise. /// Advances to the next token if the current token was matched. bool at(String token) { if (currentToken == token) { advance(); return true; } return false; } /// Advances [currentToken] if it matches [token] and raises an error otherwise. void expect(String token) { if (!at(token)) { error("expected $token but found $currentToken"); } } /// Returns true if [currentToken] is a name. bool get isName => currentToken.startsWith(new RegExp("[\\w\$_]")); /// Returns true if [currentToken] is a number. bool get isNumber => currentToken.startsWith(new RegExp("\\.?[\\d]")); /// Returns true if [currentToken] is a string. bool get isString => currentToken[0] == '"' || currentToken[0] == "'"; void error(String message) { throw new Exception(message); } } class Parser extends Scanner { Parser(String source) : super(source); // ["package" qualifiedName ";"] { importStatement } { typeDeclaration } parseCompilationUnit() { String package; if (at("package")) { package = parseQualifiedName(); expect(";"); } var imports = []; while (at("import")) { imports.add(parseImportStatement()); } var declarations = []; while (!at("")) { declarations.add(parseTypeDeclaration()); } return ["unit", package, imports, declarations]; } // "import" name {"." name} ["." "*"] ";" // no static parseImportStatement() { var names = [parseName()]; while (at(".")) { names.add(at("*") ? "*" : parseName()); } expect(";"); return ["import", names.join(".")]; } // classDeclaration | interfaceDeclaration // no enum parseTypeDeclaration() { var modifiers = parseModifiers(); if (at("class")) return parseClassDeclaration(modifiers); if (at("interface")) return parseInterfaceDeclaration(modifiers); error("expected class or interface but found $currentToken."); } // no protected, abstract, native, synchronized, transient, volatile, strictfp parseModifiers() { var modifiers = []; while (true) { if (at("public")) { modifiers.add("public"); } else if (at("private")) { modifiers.add("private"); } else if (at("static")) { modifiers.add("static"); } else if (at("final")) { modifiers.add("final"); } else break; } return ["modifiers", modifiers]; } // [modifiers] [typeParameter] "class" name ["extends" type] ["implements" type {"," type] classBody parseClassDeclaration(modifiers) { var className = parseName(); var typeParameters = at("<") ? parseTypeParameters() : null; var superclassType = at("extends") ? parseType() : null; var interfaceTypes = []; if (at("implements")) { interfaceTypes.add(parseType()); while (at(",")) { interfaceTypes.add(parseType()); } } return ["class", modifiers, className, typeParameters, superclassType, interfaceTypes, parseClassOrInterfaceBody()]; } // [modifiers] [typeParameter] "interface" name ["extends" type {"," type}] interfaceBody parseInterfaceDeclaration(modifiers) { var interfaceName = parseName(); var typeParameters = at("<") ? parseTypeParameters() : null; var interfaceTypes = []; if (at("extends")) { interfaceTypes.add(parseType()); while (at(",")) { interfaceTypes.add(parseType()); } } return ["interface", modifiers, interfaceName, typeParameters, interfaceTypes, parseClassOrInterfaceBody()]; } // "{" {memberDeclaration} "}" // should distinguish class & interface (has no static, no method bodies) parseClassOrInterfaceBody() { expect("{"); var declarations = []; while (!at("}")) { declarations.add(parseMemberDeclaration()); //print("- ${declarations.last}"); } return declarations; } parseMemberDeclaration() { if (at("<")) { parseTypeParameters(); } var modifiers = parseModifiers(); if (modifiers[1].length == 1 && modifiers[1][0] == "static" && at("{")) { return ["static_initializer", parseStatementBlock()]; } if (at("class")) return parseClassDeclaration(modifiers); if (at("interface")) return parseInterfaceDeclaration(modifiers); String name = parseName(); // could be a type or constructor if (at("(")) { // it's a constructor declaration var parameters = parseParameterList(); expect("{"); return ["constructor", modifiers, name, parameters, parseStatementBlock()]; } var type = parseArrayType(parseTypeWith(name)); name = parseName(); if (at("(")) { // it's a method declaration var parameters = parseParameterList(); type = parseArrayType(type); var statements = []; if (!at(";")) { expect("{"); statements = parseStatementBlock(); } return ["method", modifiers, type, name, parameters, statements]; } else { // must be variable declaration var declarations = parseTypeDeclarations(modifiers, type, name); expect(";"); return declarations; } } parseParameterList() { var parameters = []; while (!at(")")) { var t = parseType(); t = parseArrayType(t); bool rest = at("..."); var n = parseName(); t = parseArrayType(t); parameters.add([rest ? "rest_parameter" : "parameter", t, n]); if (at(")")) break; expect(","); } return parameters; } parseParameter() { var type = parseType(); type = parseArrayType(type); bool rest = at("..."); var name = parseName(); type = parseArrayType(type); return [rest ? "rest_parameter" : "parameter", type, name]; } parseArrayInitializer() { var elements = []; while (!at("}")) { if (at("{")) { elements.add(parseArrayInitializer()); } else { elements.add(parseExpression()); } if (at("}")) break; expect(","); } return ["array", elements]; } // types ----------------------------------------------------------------------------------------------------- parseType() { return parseTypeWith(advance()); } static const primitiveTypes = const["boolean", "byte", "char", "short", "int", "float", "long", "double", "void"]; parseTypeWith(String name) { var type; if (primitiveTypes.contains(name)) { type = ["primitive_type", name]; } else { var names = [name]; while (at(".")) { names.add(parseName()); } type = ["reference_type", names.join(".")]; if (at("<")) { var names = [at("?") ? "?" : parseName()]; while (at(",")) { names.add([at("?") ? "?" : parseName()]); } expect(">"); type = ["generic_type", type, names]; } } return type; } parseArrayType(type) { while (at("[]")) { type = ["array_type", type]; } while (at("[")) { expect("]"); type = ["array_type", type]; } return type; } parseTypeParameters() { var parameters = [parseTypeParameter()]; while (at(",")) { parameters.add(parseTypeParameter()); } expect(">"); return ["type_parameters", parameters]; } String parseTypeParameter() { return at("?") ? "?" : parseName(); } parseTypeDeclarations(modifiers, type, name) { var type2 = parseArrayType(type); var init = parseTypeInitializer(); var declarations = [["variable_declaration", modifiers, type2, name, init]]; while (at(",")) { name = parseName(); type2 = parseArrayType(type); init = parseTypeInitializer(); declarations.add(["variable_declaration", modifiers, type2, name, init]); } return ["variable_declarations", declarations]; } parseTypeInitializer() { if (at("=")) { if (at("{")) { return parseArrayInitializer(); } else { return parseExpression(); } } return null; } // statements ----------------------------------------------------------------------------------------------------- parseStatementBlock() { var statements = []; while (!at("}")) { statements.add(parseStatement()); //print("= ${statements.last}"); } return ["block", statements]; } parseStatement() { if (at("{")) return parseStatementBlock(); if (at("if")) return parseIfStatement(); if (at("do")) return parseDoWhileStatement(); if (at("while")) return parseWhileStatement(); if (at("for")) return parseForStatement(); if (at("try")) return parseTryStatement(); if (at("switch")) return parseSwitchStatement(); if (at("synchronized")) { var expression = parseParenthesizedExpression(); return ["synchronized", expression, parseStatement()]; } if (at("return")) { var expression = null; if (!at(";")) { expression = parseExpression(); expect(";"); } return ["return", expression]; } if (at("throw")) { var expression = parseExpression(); expect(";"); return ["throw", expression]; } if (at("assert")) { var e = parseExpression(); var m = at(":") ? parseExpression() : null; expect(";"); return ["assert", e, m]; } if (at("break")) { var label; if (!at(";")) { label = parseName(); expect(";"); } return ["break", label]; } if (at("continue")) { var label; if (!at(";")) { label = parseName(); expect(";"); } return ["continue", label]; } if (at(";")) return ["pass"]; var expression = parseExpression(); if (expression[0] == "variable" && at(":")) { return ["label", expression[1]]; } expect(";"); if (expression[0] == "variable_declarations") { return expression; } return ["void", expression]; } parseIfStatement() { var c = parseParenthesizedExpression(); var t = parseStatement(); var e = at("else") ? parseStatement() : null; return ["if", c, t, e]; } parseDoWhileStatement() { var s = parseStatement(); expect("while"); var e = parseParenthesizedExpression(); expect(";"); return ["dowhile", e, s]; } parseWhileStatement() { var e = parseParenthesizedExpression(); var s = parseStatement(); return ["whiledo", e, s]; } parseForStatement() { var a = [], b, c = []; expect("("); if (!at(";")) { var e = parseExpression(); if (at(":")) { // e must be variable_declarations with a single variable_declaration assert(e[0] == "variable_declarations" && e[1].length == 1); b = parseExpression(); expect(")"); var s = parseStatement(); return ["foreach", e[1][0], b, s]; } a.add(e); while (at(",")) { a.add(parseExpression()); } expect(";"); } if (!at(";")) { b = parseExpression(); expect(";"); } if (!at(")")) { c.add(parseExpression()); while (at(",")) { c.add(parseExpression()); } expect(")"); } var s = parseStatement(); return ["for", a, b, c, s]; } parseTryStatement() { var s = parseStatement(); var c = []; while (at("catch")) { expect("("); var p = parseParameter(); expect(")"); c.add(["catch", p, parseStatement()]); } var f = at("finally") ? parseStatement() : null; return ["try", s, c, f]; } parseSwitchStatement() { var expression = parseParenthesizedExpression(); expect("{"); var statements = []; while (!at("}")) { if (at("case")) { var e = parseExpression(); expect(":"); statements.add(["case", e]); } else if (at("default")) { expect(":"); statements.add(["default"]); } else { statements.add(parseStatement()); } } return ["switch", expression, statements]; } parseParenthesizedExpression() { expect("("); var expression = parseExpression(); expect(")"); return expression; } // expressions ----------------------------------------------------------------------------------------------------- static const assignmentOperators = const["=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>="]; parseExpression() { var e = parseConditionalExpression(); if (assignmentOperators.contains(currentToken)) { var operator = advance(); return [operator, e, parseExpression()]; } return e; } parseConditionalExpression() { var e = parseOrExpression(); if (at("?")) { var t = parseConditionalExpression(); expect(":"); return ["?:", e, t, parseConditionalExpression()]; } return e; } parseOrExpression() { var e = parseAndExpression(); while (at("||")) { e = ["||", e, parseAndExpression()]; } return e; } parseAndExpression() { var e = parseBitIorExpression(); while (at("&&")) { e = ["&&", e, parseBitIorExpression()]; } return e; } parseBitIorExpression() { var e = parseBitXorExpression(); while (at("|")) { e = ["|", e, parseBitXorExpression()]; } return e; } parseBitXorExpression() { var e = parseBitAndExpression(); while (at("^")) { e = ["^", e, parseBitAndExpression()]; } return e; } parseBitAndExpression() { var e = parseEqualityExpression(); while (at("&")) { e = ["&", e, parseEqualityExpression()]; } return e; } parseEqualityExpression() { var e = parseRelationalExpression(); while (true) { if (at("==")) { e = ["==", e, parseRelationalExpression()]; } else if (at("!=")) { e = ["!=", e, parseRelationalExpression()]; } else return e; } } parseRelationalExpression() { var e = parseBitShiftExpression(); while (true) { if (at("<")) { // hack: could be a variable declaration var e2 = parseBitShiftExpression(); if (e[0] == "variable" && e2[0] == "variable" && at(">")) { var type = parseArrayType(["generic_type", ["type", e[1]], [e2[1]]]); var name = parseName(); return parseTypeDeclarations(null, type, name); } e = ["<", e, e2]; } else if (at("<=")) { e = ["<=", e, parseBitShiftExpression()]; } else if (at(">")) { e = [">", e, parseBitShiftExpression()]; } else if (at(">=")) { e = [">=", e, parseBitShiftExpression()]; } else if (at("instanceof")) { e = ["instanceof", e, parseBitShiftExpression()]; } else return e; } } parseBitShiftExpression() { var e = parseAdditionExpression(); while (true) { if (at("<<")) { e = ["<<", e, parseAdditionExpression()]; } else if (at(">>")) { e = [">>", e, parseAdditionExpression()]; } else if (at(">>>")) { e = [">>>", e, parseAdditionExpression()]; } else return e; } } parseAdditionExpression() { var e = parseMultiplicationExpression(); while (true) { if (at("+")) { e = ["+", e, parseMultiplicationExpression()]; } else if (at("-")) { e = ["-", e, parseMultiplicationExpression()]; } else return e; } } parseMultiplicationExpression() { var e = parsePrefixExpression(); while (true) { if (at("*")) { e = ["*", e, parsePrefixExpression()]; } else if (at("/")) { e = ["/", e, parsePrefixExpression()]; } else if (at("%")) { e = ["%", e, parsePrefixExpression()]; } else return e; } } parsePrefixExpression() { if (at("++")) { return ["++p", parsePrefixExpression()]; } if (at("--")) { return ["--p", parsePrefixExpression()]; } if (at("+")) { return ["+p", parsePrefixExpression()]; } if (at("-")) { return ["-p", parsePrefixExpression()]; } if (at("!")) { return ["!", parsePrefixExpression()]; } if (at("~")) { return ["~", parsePrefixExpression()]; } return parsePostfixExpression(); } parsePostfixExpression() { var e = parsePrimary(); if (at("++")) { return ["p++", e]; } if (at("--")) { return ["p--", e]; } return e; } parsePrimary() { var e = parseLiteral(); while (true) { if (at("(")) { // simple call assert(e[0] == "variable"); e = ["call", null, e[1], parseArguments()]; } else if (at("[")) { // hack: could be a variable declaration if (e[0] == "variable" && at("]")) { var type = ["array_type", ["type", e[1]]]; return parseTypeDeclarations(null, parseArrayType(type), parseName()); } var index = parseExpression(); expect("]"); e = ["[]", e, index]; } else if (at(".")) { // method call or field access var name = parseName(); if (at("(")) { // method call return ["call", e, name, parseArguments()]; } e = [".", e, name]; } else return e; } } parseArguments() { var arguments = []; while (!at(")")) { arguments.add(parseExpression()); if (at(")")) break; expect(","); } return arguments; } static const literalFirst = const["++", "+", "--", "-", "!", "~", "("]; parseLiteral() { if (at("null")) return ["literal", null]; if (at("true")) return ["literal", true]; if (at("false")) return ["literal", false]; if (at("this")) return ["this"]; if (at("super")) return ["super"]; if (at("new")) { var type = parseType(); if (at("[")) { while (at("]")) { type = ["array_type", type]; expect("["); } var expression = parseExpression(); expect("]"); return ["new_array", type, expression]; } if (at("(")) { return ["new_instance", type, parseArguments()]; } error("unexpected $currentToken after new $type"); } if (at("(")) { var e = parseExpression(); expect(")"); // need to distinguish expression in parentheses from a cast if (e[0] == "variable") { if (isName || isNumber || isString || literalFirst.contains(currentToken)) { return ["cast", e[1], parsePrefixExpression()]; } } return e; } if (isString) { return ["literal", advance()]; } if (isNumber) { return ["literal", num.parse(advance())]; } // need to distinguish type declarations and variables // a sequence of two names should be a type declaration // ("foo[] bar" or "foo<x> bar" is not detected) if (isName) { var name = advance(); if (isName) { var type = parseArrayType(parseTypeWith(name)); return parseTypeDeclarations(null, type, parseName()); } return ["variable", name]; } error("unexpected $currentToken in expression"); } // utilities ------------------------------------------------------------------------------------------------------- List<String> parseNameList() { var names = [parseName()]; while (at(",")) { names.add(parseName()); } return names; } String parseQualifiedName() { var names = [parseName()]; while (at(".")) { names.add(parseName()); } return names.join("."); } String parseName() { if (!isName) { error("expected NAME but found $currentToken"); } return advance(); } } /// Translates Java nodes into Dart (printed to [stdout]). class Translator { Map _funcs; int _indent; Translator() { _indent = 0; _funcs = { "unit": (node) { if (node[1] != null) { emit("library ${node[1]};"); if (node[2].isNotEmpty) { newline(); } } node[2].forEach(translate); node[3].forEach(translate); }, "import": (node) { //Dart doesn't need Java imports //emit("import '${node[1]}';"); }, "class": (node) { newline(); emit("class ${node[2]} ${node[4] != null ? "extends ${translate(node[4])}" : ""}"); emit("{"); indent(); node[6].where((n) => n[0] != "class").forEach(translate); dedent(); emit("}"); // Dart doesn't support nested classes node[6].where((n) => n[0] == "class").forEach(translate); }, "variable_declarations": (node) { node[1].forEach((n) => emit(translate(n) + ";")); }, "variable_declaration": (node) { var init = node[4] != null ? " = ${translate(node[4])}" : ""; var mods = node[1] != null ? translate(node[1]) : ""; return "$mods${translate(node[2])} ${name(node[3])}$init"; }, "modifiers": (node) { var mods = ""; if (node[1].contains("static")) mods += "static "; if (node[1].contains("final")) mods += "final "; return mods; }, "method": (node) { newline(); var mods = node[1] != null ? translate(node[1]) : ""; emit("$mods${translate(node[2])} ${node[3]}(${node[4].map(translate).join(", ")})"); emit("{"); indent(); translate(node[5]); dedent(); emit("}"); }, "parameter": (node) { return "${translate(node[1])} ${name(node[2])}"; }, "rest_parameter": (node) { return "List<${translate(node[1])}> ${name(node[2])}"; }, "block": (node) { node[1].forEach(translate); }, "type": (node) { // TODO should be primitive_type or generic_type var s = dartType(node[1]); return s != null ? s : node[1]; }, "primitive_type": (node) { return dartType(node[1]); }, "reference_type": (node) { return node[1]; }, "array_type": (node) { return "List<${translate(node[1])}>"; }, "generic_type": (node) { var type = translate(node[1]); return node[2] != null && node[2][0] != "?" ? "$type<${node[2].join(", ")}>" : type; }, "void": (node) { emit("${strip(translate(node[1]))};"); }, "call": (node) { var receiver = node[1] != null ? translate(node[1]) : ""; if (node[1] != null && node[2] == "length" && node[3].length == 0) { return "$receiver.length"; } if (node[1] != null && node[2] == "charAt" && node[3].length == 1) { return "$receiver[${translate(node[3][0])}]"; } return "${receiver != "" ? receiver + "." : ""}${node[2]}(${node[3].map(translate).map(strip).join(", ")})"; }, "literal": (node) { if (node[1] is String) { node[1] = node[1].replaceAll("\$", "\\\$"); if (node[1][0] == "'") { // it's a char not a string return "${node[1]}.codeUnitAt(0)"; } } return "${node[1]}"; }, "variable": (node) { return name(node[1]); }, "new_array": (node) { return "new List<${translate(node[1])}>(${translate(node[2])})"; }, "new_instance": (node) { return "new ${translate(node[1])}(${node[2].map(translate).join(",")})"; }, "array": (node) { return "[${node[1].map(translate).join(", ")}]"; }, "type_parameters": (node) { return node[1].join(", "); }, // TODO use precedence to omit unneeded parentheses "+": (node) { return "(${translate(node[1])} + ${translate(node[2])})"; }, "-": (node) { return "(${translate(node[1])} - ${translate(node[2])})"; }, "*": (node) { return "(${translate(node[1])} * ${translate(node[2])})"; }, "/": (node) { return "(${translate(node[1])} ~/ ${translate(node[2])})"; }, // int only "%": (node) { return "(${translate(node[1])} % ${translate(node[2])})"; }, "&": (node) { return "(${translate(node[1])} & ${translate(node[2])})"; }, "|": (node) { return "(${translate(node[1])} | ${translate(node[2])})"; }, "^": (node) { return "(${translate(node[1])} ^ ${translate(node[2])})"; }, "<<": (node) { return "(${translate(node[1])} << ${translate(node[2])})"; }, ">>": (node) { return "(${translate(node[1])} >> ${translate(node[2])})"; }, ">>>": (node) { return "(${translate(node[1])} >> ${translate(node[2])})"; }, "&&": (node) { return "(${translate(node[1])} && ${translate(node[2])})"; }, "||": (node) { return "(${translate(node[1])} || ${translate(node[2])})"; }, "<": (node) { return "(${translate(node[1])} < ${translate(node[2])})"; }, "<=": (node) { return "(${translate(node[1])} <= ${translate(node[2])})"; }, ">": (node) { return "(${translate(node[1])} > ${translate(node[2])})"; }, ">=": (node) { return "(${translate(node[1])} >= ${translate(node[2])})"; }, "==": (node) { return "(${translate(node[1])} == ${translate(node[2])})"; }, "!=": (node) { return "(${translate(node[1])} != ${translate(node[2])})"; }, "=": (node) { return "(${translate(node[1])} = ${translate(node[2])})"; }, "+=": (node) { return "(${translate(node[1])} += ${translate(node[2])})"; }, "-=": (node) { return "(${translate(node[1])} -= ${translate(node[2])})"; }, "*=": (node) { return "(${translate(node[1])} *= ${translate(node[2])})"; }, "/=": (node) { return "(${translate(node[1])} ~/= ${translate(node[2])})"; }, // int only "[]": (node) { return "${translate(node[1])}[${strip(translate(node[2]))}]"; }, "p++": (node) { return "${translate(node[1])}++"; }, "p--": (node) { return "${translate(node[1])}--"; }, "++p": (node) { return "(++${translate(node[1])})"; }, "--p": (node) { return "(--${translate(node[1])})"; }, "!": (node) { return "(!${translate(node[1])})"; }, "~": (node) { return "(~${translate(node[1])})"; }, "-p": (node) { return "(-${translate(node[1])})"; }, "cast": (node) { //Dart doesn't support casts //return "(${translate(node[2])} as ${node[1]})"; return translate(node[2]); }, ".": (node) { return translate(node[1]) + ".${node[2]}"; }, "?:": (node) { return "(${translate(node[1])} ? ${translate(node[2])} : ${translate(node[3])})"; }, "if": (node) { emit("if (${strip(translate(node[1]))}) {"); indent(); translate(node[2]); if (node[3] != null) { dedent(); emit("} else {"); indent(); translate(node[3]); } dedent(); emit("}"); }, "return": (node) { if (node[1] != null) { emit("return ${strip(translate(node[1]))};"); } else { emit("return;"); } }, "for": (node) { var i = []; node[1].forEach((n) { if (n[0] == 'variable_declarations') i.addAll(n[1]); else i.add(n); }); if (i.length > 1) { // Dart doesn't like multiple declarations in for so we move them i.forEach(translate); i = []; } emit("for (${i.map(translate).join(", ")}; " "${node[2] != null ? strip(translate(node[2])) : ""}; " "${node[3].map(translate).join(", ")}) {"); indent(); translate(node[4]); dedent(); emit("}"); }, "foreach": (node) { emit("for (${translate(node[1])} in ${translate(node[2])}) {"); indent(); translate(node[3]); dedent(); emit("}"); }, "dowhile": (node) { emit("do {"); indent(); translate(node[2]); dedent(); emit("} while (${strip(translate(node[1]))});"); }, "whiledo": (node) { emit("while (${strip(translate(node[1]))}) {"); indent(); translate(node[2]); dedent(); emit("}"); }, "try": (node) { emit("try {"); indent(); translate(node[1]); dedent(); node[2].forEach(translate); if (node[3] != null) { emit("} finally {"); indent(); translate(node[3]); dedent(); } emit("}"); }, "catch": (node) { emit("} on ${translate(node[1][1])} catch (${name(node[1][2])}) {"); indent(); translate(node[2]); dedent(); }, "assert": (node) { emit("assert(${strip(translate(node[1]))});"); }, "break": (node) { if (node[1] != null) { emit("break ${node[1]};"); } emit("break;"); }, "continue": (node) { if (node[1] != null) { emit("continue ${node[1]};"); } emit("continue;"); }, "switch": (node) { emit("switch (${translate(node[1])}) {"); indent(); indent(); node[2].forEach(translate); dedent(); dedent(); emit("}"); }, "case": (node) { dedent(); emit("case ${translate(node[1])}:"); indent(); }, "default": (node) { dedent(); emit("default:"); indent(); }, "label": (node) { emit("${node[1]}:"); }, "throw": (node) { emit("throw ${translate(node[1])};"); }, }; } String dartType(String type) { // map Java type to Dart type return const{ "boolean": "bool", "byte": "int", "char": "int", "short": "int", "int": "int", "float": "double", "long": "int", "double": "double", "void" : "void" }[type]; } String name(String name) { // do not use Dart reserved words if (const["is"].contains(name)) name += "_"; return name; } String strip(String s) { // ignore parentheses on top level if (s.startsWith("(") && s.endsWith(")")) { return s.substring(1, s.length - 1); } return s; } void newline() { stdout.write("\n"); } void emit(String line) { for (int i = 0; i < _indent; i++) { stdout.write(' '); } stdout.writeln(line); } void indent() { _indent++; } void dedent() { _indent--; } translate(node) { return _funcs[node[0]](node); } } void main(List<String> args) { if (args.length > 0) { var source = new File("/Users/sma/Desktop/Atlantis.java").readAsStringSync(); var node = new Parser(source).parseCompilationUnit(); new File("/tmp/json").writeAsStringSync(JSON.encode(node)); } else { var node = JSON.decode(new File("/tmp/json").readAsStringSync()); new Translator().translate(node); } }