// 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 _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 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 imports, List declarations */ static const Import = "import"; /* String qualifiedName */ static const Modifiers = "modifiers"; /* List modifiers */ static const Class = "class"; /* AST modifiers, String className, AST typeParameters, AST superclassType, List interfaceTypes, List classBody */ static const Interface = "interface"; /* AST modifiers, String interfaceName, AST typeParameters, List interfaceTypes, List classBody */ static const StaticInitializer = "static_initializer"; /* AST block */ static const Constructor = "constructor"; /* AST modifiers, String constructorName, List parameters, List throws, AST block */ static const Method = "method"; /* AST modifiers, AST returnType, String methodName, List parameters, List 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 declarations */ static const ArrayInitializer = "array"; /* List 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 parameters */ // statements static const Block = "block"; /* List 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> _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 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); } }