/* Copyright (C) 2004 - 2008 Versant Inc. http://www.db4o.com This file is part of the sharpen open source java to c# translator. sharpen is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation and as clarified by db4objects' GPL interpretation policy, available at http://www.db4o.com/about/company/legalpolicies/gplinterpretation/ Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street, Suite 350, San Mateo, CA 94403, USA. sharpen is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package sharpen.core.haxe; import java.io.*; import java.util.*; import java.util.regex.*; import sharpen.core.csharp.ast.*; import sharpen.core.io.*; public class HaxePrinter extends CSVisitor { protected IndentedWriter _writer; protected CSTypeDeclaration _currentType; private int _lastPrintedCommentIndex; private List<CSLineComment> _comments; public HaxePrinter() { } public void setWriter(Writer writer) { _writer = new IndentedWriter(writer); } public void print(CSCompilationUnit node) { _lastPrintedCommentIndex = 0; _comments = node.comments(); try { node.accept(this); } finally { _currentType = null; _comments = null; } } private List<CSUsing> printableUsingList(Iterable<CSUsing> usings) { List<CSUsing> list = new ArrayList<CSUsing>(); for (CSUsing using : usings) { list.add(using); } Collections.sort(list, new Comparator<CSUsing>() { public int compare(CSUsing a, CSUsing b) { boolean ia = a.namespace().startsWith("System"); boolean ib = b.namespace().startsWith("System"); if (ia && ib) return a.namespace().compareTo(b.namespace()); else if (ia) return -1; else if (ib) return 1; else return a.namespace().compareTo(b.namespace()); } }); return list; } static final Pattern META_VARIABLE_PATTERN = Pattern.compile("\\$(\\w+)"); @Override public void visit(CSMacroExpression node) { node.macro().accept(this); } @Override public void visit(CSMacroTypeReference node) { node.macro().accept(this); } @Override public void visit(CSMacro node) { final String template = node.template(); final Matcher matcher = META_VARIABLE_PATTERN.matcher(template); int last = 0; while (matcher.find()) { write(template.substring(last, matcher.start())); Object value = node.resolveVariable(matcher.group(1)); // value is either a single node or a list of nodes if (value instanceof CSNode) { ((CSNode) value).accept(this); } else { writeCommaSeparatedList((Iterable<CSNode>) value); } last = matcher.end(); } write(template.substring(last)); } public void visit(CSCompilationUnit node) { beginEnclosingIfDefs(node); if (null != node.namespace()) { writeLine("package " + node.namespace() + ";"); writeLine(); } List<CSUsing> usings = printableUsingList(node.usings()); for (CSUsing using : usings) { using.accept(this); } if (usings.size() > 0) _writer.writeLine(); writeLineSeparatedList(node.types()); endEnclosingIfDefs(node); } @Override public void visit(CSRemovedExpression node) { throw new IllegalStateException("Unexpected removal of expression: " + node.toString()); } public void visit(CSUsing node) { writeLine("import " + node.namespace() + ";"); } public void visit(CSClass node) { writeType(node); } public void visit(CSEnum node) { writeMemberHeader(node); writeLine("enum " + node.name()); enterBody(); writeSeparatedList(node.values(), new Closure() { public void execute() { writeLine(";"); } }); writeLine(); leaveBody(); } @Override public void visit(CSEnumValue node) { writeIndented(node.name()); } public void visit(CSStruct node) { writeType(node); } public void visit(CSInterface node) { writeType(node); } public void visit(CSTypeParameter node) { write(node.name()); if (node.superClass() != null) { write(" : ("); node.superClass().accept(this); write(")"); } } @Override public void visit(final CSArrayTypeReference node) { for (int i = 0; i < node.dimensions(); ++i) { write("Array<"); } node.elementType().accept(this); for (int i = 0; i < node.dimensions(); ++i) { write(">"); } } public void visit(CSTypeReference node) { write(node.typeName()); writeTypeArguments(node); } private void writeTypeArguments(CSTypeArgumentProvider node) { final List<CSTypeReferenceExpression> typeArgs = node.typeArguments(); if (!typeArgs.isEmpty()) { writeGenericParameters(typeArgs); } } public void visit(CSDelegate node) { writeMemberHeader(node); write("typedef "); write(node.name()); write(" = "); for (CSVariableDeclaration parameter : node.parameters()) { parameter.type().accept(this); write("->"); } write("Void"); writeLine(";"); } private void writeTypeHeader(CSTypeDeclaration node) { writeMemberHeader(node); if (node.isInterface()) { if (node.partial()) _writer.write("/*partial*/ "); write("interface " + node.name()); } else if (node instanceof CSClass) { CSClass classNode = (CSClass) node; write(classModifier(classNode.modifier())); if (node.partial()) _writer.write("/*partial*/ "); write("class " + node.name()); } else { write("/*struct*/ " + node.name()); } writeTypeParameters(node); writeBaseTypes(node); } private void writeMemberHeader(CSTypeDeclaration node) { writeAttributes(node); // writeVisibility(node); } private void writeTypeParameters(CSTypeParameterProvider node) { final List<CSTypeParameter> parameters = node.typeParameters(); if (parameters.isEmpty()) return; writeGenericParameters(parameters); } private <T extends CSNode> void writeGenericParameters(Iterable<T> nodes) { write("<"); writeCommaSeparatedList(nodes); write(">"); } private void writeType(CSTypeDeclaration node) { writeDoc(node); beginEnclosingIfDefs(node); writeTypeHeader(node); writeTypeBody(node); endEnclosingIfDefs(node); } private void writeBaseTypes(CSTypeDeclaration node) { if(node.baseType() != null) { write(" extends "); node.baseType().accept(this); } List<CSTypeReferenceExpression> baseTypes = node.interfaces(); if (baseTypes.isEmpty()) { return; } for (int i = 0; i < baseTypes.size(); i++) { CSTypeReferenceExpression baseType = baseTypes.get(i); if (i > 0 || node.baseType() != null) { write(","); } write(" implements "); baseType.accept(this); } } private void writeTypeBody(CSTypeDeclaration node) { writeLine(); enterBody(); // generate default constructor if needed if(!node.isInterface()) { boolean created = false; CSConstructorInvocationExpression chained = null; for (CSConstructor constructor : node.constructors()) { if(constructor.parameters().isEmpty()) { writeVisibility(node); chained = constructor.chainedConstructorInvocation(); created = true; break; } } if(!created) { writeIndentation(); boolean isAbstract = (node instanceof CSClass) && (((CSClass)node).modifier() == CSClassModifier.Abstract); writeVisibility(isAbstract ? CSVisibility.Protected : CSVisibility.Public); } writeLine("function new()"); enterBody(); if(chained != null) { writeIndentedLine("super();"); } leaveBody(); writeLine(); } CSTypeDeclaration saved = _currentType; _currentType = node; writeLineSeparatedList(node.members()); _currentType = saved; printPrecedingComments(node.startPosition() + node.sourceLength()); leaveBody(); } private void writeVisibility(CSMember member) { writeIndentation(); if (member.isNewModifier()) write("/*new*/ "); if (isExplicitMember(member)) return; writeVisibility(member.visibility()); } private void writeVisibility(CSVisibility visibility) { switch (visibility) { case Internal: write("public"); break; case Private: write("private"); break; case Protected: write("private"); break; case Public: write("public"); break; } write(" "); } private boolean isExplicitMember(CSMember member) { return member.name().indexOf('.') != -1; } public void visit(CSVariableDeclaration node) { if (!node.parameter()) { write("var "); } if (null != node.name()) { write(node.name()); } write(" : "); node.type().accept(this); if (null != node.initializer()) { write(" = "); node.initializer().accept(this); } } public void visit(CSConstructor node) { writeDoc(node); writeAttributes(node); if (node.isStatic()) { writeIndented("static function __init__()"); node.body().accept(this); } else { writeVisibility(node); write("function " + node.constructorMethod()); writeParameterList(node); writeLine(); node.body().addStatement(new CSReturnStatement(node.body().startPosition(), new CSThisExpression())); node.body().accept(this); } } public void visit(CSDestructor node) { } public void visit(CSMethod node) { printPrecedingComments(node); beginEnclosingIfDefs(node); writeDoc(node); writeAttributes(node); writeMethodHeader(node, node.modifier()); write("function "); writeMethodName(node); writeTypeParameters(node); writeParameterList(node); write(" : "); node.returnType().accept(this); if (_currentType instanceof CSInterface) { writeLine(";"); } else if (node.isAbstract()) { writeLine("{ throw \"abstract\"; }"); } else { writeMethodBody(node); } endEnclosingIfDefs(node); } private void endEnclosingIfDefs(CSNode node) { for (String expression : node.enclosingIfDefs()) { writeIndented("#end // "); writeLine(expression); } } private void beginEnclosingIfDefs(CSNode node) { for (String expression : node.enclosingIfDefs()) { writeIndented("#if "); writeLine(expression); } } private void writeMethodHeader(CSMember member, CSMethodModifier modifiers) { if (!_currentType.isInterface()) { writeVisibility(member); write(methodModifier(modifiers)); } else { writeIndentation(); } } protected void writeMethodBody(CSMethod node) { writeLine(); node.body().accept(this); } protected void writeMethodName(CSMethod node) { write(node.name()); } public void visit(CSBlock node) { enterBody(); visitList(node.statements()); leaveBody(); } public void visit(CSDeclarationStatement node) { printPrecedingComments(node); writeIndentation(); node.declaration().accept(this); writeLine(";"); } public void visit(CSDeclarationExpression node) { node.declaration().accept(this); } private void writeDeclaration(CSTypeReferenceExpression type, String name, CSExpression initializer) { write("var "); write(name); write(" : "); type.accept(this); if (null != initializer) { write(" = "); initializer.accept(this); } writeLine(";"); } @Override public void visit(CSLineComment node) { writeIndentedLine(node.text()); } public void visit(CSReturnStatement node) { printPrecedingComments(node); if (null == node.expression()) { writeIndentedLine("return;"); } else { writeIndented("return "); node.expression().accept(this); writeLine(";"); } } private void printPrecedingComments(CSNode node) { printPrecedingComments(node.startPosition()); } private void printPrecedingComments(int startPosition) { if (startPosition <= 0) return; if (_lastPrintedCommentIndex >= _comments.size()) return; _lastPrintedCommentIndex = printCommentsBetween( _lastPrintedCommentIndex, startPosition); } private int printCommentsBetween(int lastIndex, int endStartPosition) { int endIndex = commentIndexAfter(lastIndex, endStartPosition); if (endIndex == -1) { endIndex = _comments.size(); } visitList(_comments.subList(lastIndex, endIndex)); return endIndex; } private int commentIndexAfter(int startIndex, int endStartPosition) { for (int i = startIndex; i < _comments.size(); ++i) { if (_comments.get(i).startPosition() > endStartPosition) { return i; } } return -1; } public void visit(CSIfStatement node) { printPrecedingComments(node); writeIndented("if ("); node.expression().accept(this); writeLine(")"); node.trueBlock().accept(this); if (!node.falseBlock().isEmpty()) { writeIndentedLine("else"); node.falseBlock().accept(this); } } public void visit(CSLockStatement node) { node.body().accept(this); } public void visit(CSWhileStatement node) { writeBlockStatement("while", node); } public void visit(CSSwitchStatement node) { writeIndented("switch ("); node.expression().accept(this); writeLine(")"); enterBody(); writeLineSeparatedList(node.caseClauses()); leaveBody(); } public void visit(CSCaseClause node) { int clauses = 0; writeIndented("case "); for (CSExpression e : node.expressions()) { if (clauses++ > 0) { write(", "); } e.accept(this); } write(":"); if (node.isDefault()) { if (clauses > 0) { writeLine(); } writeIndented("default:"); } writeLine(); node.body().accept(this); } public void visit(CSForEachStatement node) { printPrecedingComments(node); writeIndented("for ("); node.variable().accept(this); write(" in "); node.expression().accept(this); writeLine(")"); node.body().accept(this); } public void visit(CSForStatement node) { printPrecedingComments(node); /** * <code> * { * <initializers> * if( <condition> ) * do * { * <body> * } while( { <updaters>; <condition> }); * </code> */ enterBody(); for (CSExpression initializer : node.initializers()) { initializer.accept(this); writeLine(";"); } if (null != node.expression()) { writeIndented("if ("); node.expression().accept(this); writeLine(")"); } writeIndentedLine("do"); node.body().accept(this); writeIndented("while ( {"); for (CSExpression updater : node.updaters()) { updater.accept(this); write(";"); } if (null != node.expression()) { node.expression().accept(this); } else { write("true"); } writeLine("} );"); leaveBody(); } public void visit(CSBreakStatement node) { printPrecedingComments(node); writeIndentedLine("break;"); } public void visit(CSGotoStatement node) { printPrecedingComments(node); if (node.target() != null) { writeIndented("/*goto case "); node.target().accept(this); write(";*/"); writeLine(); } else writeIndentedLine("/*goto " + node.label() + ";*/"); } public void visit(CSContinueStatement node) { printPrecedingComments(node); writeIndentedLine("continue;"); } private void writeBlockStatement(String keyword, CSBlockStatement node) { printPrecedingComments(node); writeIndented(keyword); write(" ("); node.expression().accept(this); write(")"); writeLine(); node.body().accept(this); } public void visit(CSDoStatement node) { writeIndentedLine("do"); node.body().accept(this); writeIndented("while ("); node.expression().accept(this); writeLine(");"); } public void visit(CSTryStatement node) { printPrecedingComments(node); writeIndentedLine("try"); node.body().accept(this); visitList(node.catchClauses()); // TODO: find solution for finally in haxe if (null != node.finallyBlock()) { writeIndentedLine("finally"); node.finallyBlock().accept(this); } } public void visit(CSCatchClause node) { writeIndented("catch"); CSVariableDeclaration ex = node.exception(); if (ex != null) { write(" ("); ex.accept(this); write(")"); } writeLine(); node.body().accept(this); } public void visit(CSThrowStatement node) { printPrecedingComments(node); if (null == node.expression()) { writeIndentedLine("throw;"); } else { writeIndented("throw "); node.expression().accept(this); writeLine(";"); } } public void visit(CSExpressionStatement node) { printPrecedingComments(node); writeIndentation(); node.expression().accept(this); writeLine(";"); } public void visit(CSParenthesizedExpression node) { write("("); node.expression().accept(this); write(")"); } public void visit(CSConditionalExpression node) { node.expression().accept(this); write(" ? "); node.trueExpression().accept(this); write(" : "); node.falseExpression().accept(this); } public void visit(CSInfixExpression node) { node.lhs().accept(this); write(" "); write(node.operator()); write(" "); node.rhs().accept(this); } public void visit(CSPrefixExpression node) { write(node.operator()); node.operand().accept(this); } public void visit(CSPostfixExpression node) { node.operand().accept(this); write(node.operator()); } public void visit(CSConstructorInvocationExpression node) { if(node.constructorMethod() == null) { // if we don't generate the constructor ourselves, we invoke it normally write("new "); writeMethodInvocation(node); } else if((node.expression() instanceof CSThisExpression) || (node.expression() instanceof CSBaseExpression)){ // for chained constructor invocations we call the according ctor method node.expression().accept(this); write("."); write(node.constructorMethod()); writeParameterList(node.arguments()); } else { // for normal constructor calles of own constructors we call the default constructor // and call the according ctor method write("new "); node.expression().accept(this); writeTypeArguments(node); write("()."); write(node.constructorMethod()); writeParameterList(node.arguments()); } } public void visit(CSMethodInvocationExpression node) { writeMethodInvocation(node); } protected void writeMethodInvocation(CSMethodInvocationExpression node) { node.expression().accept(this); writeTypeArguments(node); writeParameterList(node.arguments()); } public void visit(CSNumberLiteralExpression node) { write(node.token()); } public void visit(CSUncheckedExpression node) { // write("unchecked("); node.expression().accept(this); // write(")"); } public void visit(CSTypeofExpression node) { // write("typeof("); node.type().accept(this); // write(")"); } public void visit(CSBoolLiteralExpression node) { write(Boolean.toString(node.booleanValue())); } public void visit(CSStringLiteralExpression node) { write(node.escapedValue()); } public void visit(CSCharLiteralExpression node) { write(node.escapedValue()); } public void visit(CSNullLiteralExpression node) { write("null"); } public void visit(CSBaseExpression node) { write("super"); } public void visit(CSThisExpression node) { write("this"); } public void visit(CSArrayCreationExpression node) { if (node.initializer() != null) { write("ArrayUtils.CreateAndInit"); } else { write("ArrayUtils.Create"); } // HACK: predetect if multidimensional array and add indicator if (node.elementType() instanceof CSArrayTypeReference) { CSArrayTypeReference csArrayTypeReference = (CSArrayTypeReference) node .elementType(); write(Integer.toString(csArrayTypeReference.dimensions())); } write("<"); node.elementType().accept(this); write(">("); if (null != node.length()) { node.length().accept(this); } else { write("-1"); } if (null != node.initializer()) { write(", "); node.initializer().accept(this); } write(")"); } public void visit(CSArrayInitializerExpression node) { write("[ "); writeCommaSeparatedList(filterRemovedExpressions(node.expressions())); write(" ]"); } private Iterable<CSNode> filterRemovedExpressions( List<CSExpression> expressions) { final ArrayList<CSNode> result = new ArrayList<CSNode>( expressions.size()); for (CSNode e : expressions) if (!(e instanceof CSRemovedExpression)) result.add(e); return result; } public void visit(CSIndexedExpression node) { node.expression().accept(this); write("["); writeCommaSeparatedList(node.indexes()); write("]"); } public void visit(CSCastExpression node) { write("("); node.type().accept(this); write(")"); if (null != node.expression()) { node.expression().accept(this); } } public void visit(CSReferenceExpression node) { write(node.name()); } public void visit(CSMemberReferenceExpression node) { node.expression().accept(this); write("."); write(node.name()); } protected void writeParameterList(CSMethodBase node) { List<CSVariableDeclaration> parameters = node.parameters(); write("("); if (node.isVarArgs()) { if (parameters.size() > 1) { writeCommaSeparatedList(parameters.subList(0, parameters.size() - 1)); write(", "); } write("/*params*/ "); visit(parameters.get(parameters.size() - 1)); } else { writeCommaSeparatedList(parameters); } write(")"); } protected <T extends CSNode> void writeParameterList(Iterable<T> parameters) { write("("); writeCommaSeparatedList(parameters); write(")"); } public void visit(CSField node) { writeMemberHeader(node); writeFieldModifiers(node); writeDeclaration(node.type(), node.name(), node.initializer()); } public void visit(CSProperty node) { if (node.getter() != null) { write("private function __get"); write(node.name()); write("() : "); node.type().accept(this); writeLine(); if (node.isAbstract()) { writeIndentedLine("{ throw \"abstract\"; }"); } else { node.getter().accept(this); } } if (node.setter() != null) { write("private function __set"); write(node.name()); write("(value : "); node.type().accept(this); writeLine(") : Void"); if (node.isAbstract()) { writeIndentedLine("{ throw \"abstract\"; }"); } else { node.setter().accept(this); } } writeMetaMemberHeader(node); write(" var "); writeLine(node.name()); write("("); if (node.getter() == null) { write("never"); } else { write("__get"); write(node.name()); } write(","); if (node.setter() == null) { write("never"); } else { write("__set"); write(node.name()); } write(")"); write(" : "); node.type().accept(this); write(" "); } private void writeMemberHeader(CSMember node) { writeDoc(node); writeAttributes(node); writeVisibility(node); } private void writeMetaMemberHeader(CSMetaMember node) { writeDoc(node); writeAttributes(node); writeMethodHeader(node, node.modifier()); } public void visit(CSAttribute node) { writeIndented("@:"); write(node.name()); if (!node.arguments().isEmpty()) { writeParameterList(node.arguments()); } writeLine(); } @Override public void visit(CSLabelStatement node) { writeLine("/*" + node.label() + ": */"); } @Override public void visit(CSDocTextOverlay node) { writeXmlDoc(node.text()); } public void visit(CSDocTextNode node) { writeXmlDoc(xmlEscape(node.text())); } private void writeXmlDoc(final String xmldocText) { String[] lines = xmldocText.split("\n"); for (int i = 0; i < lines.length; ++i) { if (i > 0) { writeLine(); writeIndentation(); } writeBlock(lines[i].trim().replace("<br>", "<br />")); } } private String xmlEscape(String text) { return text.replaceAll("(<)(/?[^\\s][^>]*)(>)", ":lt:$2:gt:") .replace("<", "<").replace(">", ">").replace(":lt:", "<") .replace(":gt:", ">"); } public void visit(CSDocTagNode node) { String tagName = node.tagName(); List<CSDocAttributeNode> attributes = node.attributes(); List<CSDocNode> fragments = node.fragments(); write("<"); write(tagName); if (!attributes.isEmpty()) { for (CSDocAttributeNode attr : attributes) { write(" "); write(attr.name()); write("=\""); write(attr.value()); write("\""); } } write(">"); if (fragments.size() > 1) { writeLine(); for (CSDocNode f : fragments) { writeIndentation(); f.accept(this); writeLine(); } writeIndented("</" + tagName + ">"); } else { if (!fragments.isEmpty()) { fragments.get(0).accept(this); } write("</" + tagName + ">"); } } private void writeAttributes(CSMember node) { visitList(node.attributes()); } private void writeFieldModifiers(CSField node) { for (CSFieldModifier m : node.modifiers()) { String s = m.toString().toLowerCase(); if (s.equals("const")) { write("inline "); } else if (s.equals("readonly")) { } else { write(m.toString().toLowerCase()); write(" "); } } } private void writeDoc(CSMember node) { List<CSDocNode> docs = node.docs(); if (docs.isEmpty()) { return; } linePrefix("/// "); for (CSDocNode doc : docs) { writeIndentation(); doc.accept(this); writeLine(); } linePrefix(null); } private String methodModifier(CSMethodModifier modifier) { switch (modifier) { case Static: return "static "; case Virtual: return ""; case Abstract: return "/*abstract*/ "; case AbstractOverride: return "/*abstract*/ override "; case Sealed: return "/*sealed*/ override "; case Override: return "override "; } return ""; } interface Closure { void execute(); } private <T extends CSNode> void writeLineSeparatedList(Iterable<T> nodes) { writeSeparatedList(nodes, new Closure() { public void execute() { writeLine(); } }); } private <T extends CSNode> void writeCommaSeparatedList(Iterable<T> nodes) { writeList(nodes, ", "); } private <T extends CSNode> void writeList(Iterable<T> nodes, final String separator) { writeSeparatedList(nodes, new Closure() { public void execute() { write(separator); } }); } private <T extends CSNode> void writeSeparatedList(Iterable<T> nodes, Closure separator) { Iterator<T> iterator = nodes.iterator(); if (!iterator.hasNext()) return; iterator.next().accept(this); while (iterator.hasNext()) { separator.execute(); iterator.next().accept(this); } } private String classModifier(CSClassModifier modifier) { switch (modifier) { case Abstract: return "/*abstract*/ "; case Sealed: return "/*sealed*/ "; } return ""; } protected void enterBody() { // writeLine(); writeIndentedLine("{"); indent(); } private void indent() { _writer.indent(); } private void outdent() { _writer.outdent(); } private void writeIndentation() { _writer.writeIndentation(); } private void writeIndented(String s) { _writer.writeIndented(s); } private void writeIndentedLine(String s) { _writer.writeIndentedLine(s); } private void write(String s) { _writer.write(s); } private void linePrefix(String s) { _writer.linePrefix(s); } private void writeBlock(String s) { _writer.writeBlock(s); } private void writeLine(String s) { _writer.writeLine(s); } private void writeLine() { _writer.writeLine(); } protected void leaveBody() { outdent(); writeIndentedLine("}"); } class CSharpTypeReferenceVisitor extends CSVisitor { private CSVisitor _delegate; CSharpTypeReferenceVisitor(CSVisitor delegate) { _delegate = delegate; } @Override public void visit(CSArrayTypeReference node) { node.elementType().accept(_delegate); } public void visit(CSTypeReferenceExpression node) { node.accept(_delegate); } public void visit(CSTypeReference node) { node.accept(_delegate); } } }