Skip to content

Instantly share code, notes, and snippets.

@adam-singer
Forked from sma/README.md
Created December 31, 2013 04:35
Show Gist options
  • Save adam-singer/8192664 to your computer and use it in GitHub Desktop.
Save adam-singer/8192664 to your computer and use it in GitHub Desktop.

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

// 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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment