Skip to content

Instantly share code, notes, and snippets.

@sma
Last active July 4, 2023 06:28
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save sma/8180927 to your computer and use it in GitHub Desktop.
Save sma/8180927 to your computer and use it in GitHub Desktop.
This is an ad-hoc Java-to-Dart translator written in three days. This is version 2 which some bug fixes.

Java to Dart

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. 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.

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.

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 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.

Stefan

// 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;
}
// 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;
int position;
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+)?[lLfF]?|' // 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()) {
Match m = _matches.current;
if (m[1] != null) {
position = m.input.substring(0, m.start).split("\n").length;
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.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} ["." "*"] ";"
// no static
parseImportStatement() {
var names = [parseName()];
while (at(".")) {
names.add(at("*") ? "*" : parseName());
}
expect(";");
return [AST.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.");
}
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(",");
}
return parameters;
}
parseParameter() {
var type = parseType();
type = parseArrayType(type);
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() {
var elements = [];
while (!at("}")) {
if (at("{")) {
elements.add(parseArrayInitializer());
} else {
elements.add(parseExpression());
}
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;
}
parseTypeParameters() {
var parameters = [parseTypeParameter()];
while (at(",")) {
parameters.add(parseTypeParameter());
}
expect(">");
return [AST.TypeParameters, parameters];
}
String parseTypeParameter() {
return at("?") ? "?" : parseQualifiedName();
}
// statements -----------------------------------------------------------------------------------
parseStatementBlock() {
return [AST.Block, parseWhileNot("}", parseStatement)];
}
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;
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];
}
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 ["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];
}
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 = andSemicolon(parseExpression());
}
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 = parseIfAt("finally", parseStatement);
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 (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("<=")) {
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", 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;
}
}
parseArguments() {
var arguments = [];
while (!at(")")) {
arguments.add(parseExpression());
if (at(")")) break;
expect(",");
}
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("[")) {
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("[");
}
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) {
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
// ("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];
}
error("unexpected $currentToken in expression");
}
// utilities -------------------------------------------------------------------------------------------------------
String parseQualifiedName() {
return parseList(parseName, separator: ".").join(".");
}
String parseName() {
if (!isName) {
error("expected NAME but found $currentToken");
}
return advance();
}
}
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) {
newline();
}
}
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) {
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 _dartName(node[1]);
},
"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(", ")})";
},
// 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
"&=": _binaryExpression("&="),
"|=": _binaryExpression("|="),
"[]": (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) {
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((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]);
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 (${_dartName(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])};");
},
"pass": (node) {
},
};
_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",
"byte": "int",
"char": "int",
"short": "int",
"int": "int",
"float": "double",
"long": "int",
"double": "double",
"void" : "void"
}[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);
}
return s;
}
void newline() {
_sink.write("\n");
}
void emit(String line) {
for (int i = 0; i < _indent; i++) {
_sink.write(' ');
}
_sink.writeln(line);
}
void indent() { _indent++; }
void dedent() { _indent--; }
translate(node) {
return _funcs[node[0]](node);
}
}
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();
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);
}
}
@kametic
Copy link

kametic commented Jan 9, 2014

Hi , you might have used the java7 grammar from sablecc. Sablecc is quite easy to use and the grammar cover all the java language including java 7 specificities.

http://sablecc3.sablecc.org/wiki/Java-1.7

@Allanbikundo
Copy link

Hello I just wanted to say thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment