package net.jangaroo.jooc.backend; import net.jangaroo.jooc.CodeGenerator; import net.jangaroo.jooc.Debug; import net.jangaroo.jooc.JooSymbol; import net.jangaroo.jooc.Jooc; import net.jangaroo.jooc.JoocProperties; import net.jangaroo.jooc.JsWriter; import net.jangaroo.jooc.SyntacticKeywords; import net.jangaroo.jooc.ast.Annotation; import net.jangaroo.jooc.ast.AnnotationParameter; import net.jangaroo.jooc.ast.ApplyExpr; import net.jangaroo.jooc.ast.ArrayIndexExpr; import net.jangaroo.jooc.ast.ArrayLiteral; import net.jangaroo.jooc.ast.AsExpr; import net.jangaroo.jooc.ast.AssignmentOpExpr; import net.jangaroo.jooc.ast.BlockStatement; import net.jangaroo.jooc.ast.BreakStatement; import net.jangaroo.jooc.ast.CaseStatement; import net.jangaroo.jooc.ast.Catch; import net.jangaroo.jooc.ast.ClassBody; import net.jangaroo.jooc.ast.ClassDeclaration; import net.jangaroo.jooc.ast.CommaSeparatedList; import net.jangaroo.jooc.ast.CompilationUnit; import net.jangaroo.jooc.ast.ContinueStatement; import net.jangaroo.jooc.ast.Declaration; import net.jangaroo.jooc.ast.DefaultStatement; import net.jangaroo.jooc.ast.Directive; import net.jangaroo.jooc.ast.DoStatement; import net.jangaroo.jooc.ast.DotExpr; import net.jangaroo.jooc.ast.EmptyDeclaration; import net.jangaroo.jooc.ast.EmptyStatement; import net.jangaroo.jooc.ast.Expr; import net.jangaroo.jooc.ast.Extends; import net.jangaroo.jooc.ast.ForInStatement; import net.jangaroo.jooc.ast.ForInitializer; import net.jangaroo.jooc.ast.ForStatement; import net.jangaroo.jooc.ast.FunctionDeclaration; import net.jangaroo.jooc.ast.FunctionExpr; import net.jangaroo.jooc.ast.Ide; import net.jangaroo.jooc.ast.IdeDeclaration; import net.jangaroo.jooc.ast.IdeExpr; import net.jangaroo.jooc.ast.IdeWithTypeParam; import net.jangaroo.jooc.ast.IfStatement; import net.jangaroo.jooc.ast.Implements; import net.jangaroo.jooc.ast.ImportDirective; import net.jangaroo.jooc.ast.InfixOpExpr; import net.jangaroo.jooc.ast.Initializer; import net.jangaroo.jooc.ast.LabeledStatement; import net.jangaroo.jooc.ast.NamespaceDeclaration; import net.jangaroo.jooc.ast.NamespacedIde; import net.jangaroo.jooc.ast.NewExpr; import net.jangaroo.jooc.ast.ObjectField; import net.jangaroo.jooc.ast.ObjectLiteral; import net.jangaroo.jooc.ast.PackageDeclaration; import net.jangaroo.jooc.ast.Parameter; import net.jangaroo.jooc.ast.Parameters; import net.jangaroo.jooc.ast.ParenthesizedExpr; import net.jangaroo.jooc.ast.QualifiedIde; import net.jangaroo.jooc.ast.ReturnStatement; import net.jangaroo.jooc.ast.SemicolonTerminatedStatement; import net.jangaroo.jooc.ast.Statement; import net.jangaroo.jooc.ast.SuperConstructorCallStatement; import net.jangaroo.jooc.ast.SwitchStatement; import net.jangaroo.jooc.ast.ThrowStatement; import net.jangaroo.jooc.ast.TryStatement; import net.jangaroo.jooc.ast.Type; import net.jangaroo.jooc.ast.TypeRelation; import net.jangaroo.jooc.ast.TypedIdeDeclaration; import net.jangaroo.jooc.ast.UseNamespaceDirective; import net.jangaroo.jooc.ast.VariableDeclaration; import net.jangaroo.jooc.ast.VectorLiteral; import net.jangaroo.jooc.ast.WhileStatement; import net.jangaroo.jooc.config.DebugMode; import net.jangaroo.jooc.config.JoocConfiguration; import net.jangaroo.jooc.sym; import net.jangaroo.utils.CompilerUtils; import java.io.File; import java.io.IOException; import net.jangaroo.jooc.util.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * A visitor of the AST that generates executable JavaScript code on * a {@link net.jangaroo.jooc.JsWriter}. */ public class JsCodeGenerator extends CodeGeneratorBase { private static final JooSymbol SYM_VAR = new JooSymbol(sym.VAR, "var"); // NOSONAR introducing a constant for "var" would obscure the generated output private static final JooSymbol SYM_SEMICOLON = new JooSymbol(sym.SEMICOLON, ";"); private static final JooSymbol SYM_LBRACE = new JooSymbol(sym.LBRACE, "{"); private static final JooSymbol SYM_RBRACE = new JooSymbol(sym.RBRACE, "}"); public static final Set<String> PRIMITIVES = new HashSet<String>(4); static { PRIMITIVES.add("Boolean"); PRIMITIVES.add("String"); PRIMITIVES.add("Number"); PRIMITIVES.add("int"); PRIMITIVES.add("uint"); PRIMITIVES.add("Object"); PRIMITIVES.add("RegExp"); PRIMITIVES.add("Date"); PRIMITIVES.add("Array"); PRIMITIVES.add("Error"); PRIMITIVES.add("Vector"); PRIMITIVES.add("Class"); PRIMITIVES.add("XML"); } private boolean expressionMode = false; private CompilationUnit compilationUnit; private final MessageFormat VAR_$NAME_EQUALS_ARGUMENTS_SLICE_$INDEX = new MessageFormat("var {0}=Array.prototype.slice.call(arguments{1,choice,0#|0<,{1}});"); private void generateToArrayCode(String paramName, int paramIndex) throws IOException { out.write(VAR_$NAME_EQUALS_ARGUMENTS_SLICE_$INDEX.format(paramName, paramIndex)); } private final CodeGenerator ARGUMENT_TO_ARRAY_CODE_GENERATOR = new CodeGenerator() { @Override public void generate(JsWriter out, boolean first) throws IOException { generateToArrayCode(FunctionExpr.ARGUMENTS, 0); } }; public JsCodeGenerator(JsWriter out) { super(out); } private void writeThis(Ide ide) throws IOException { out.writeToken(ide.isRewriteThis() ? "this$" : "this"); } private void generateIdeCodeAsExpr(Ide ide) throws IOException { if (out.isWritingComment()) { out.writeSymbol(ide.getIde()); return; } out.writeSymbolWhitespace(ide.getIde()); if (ide.isSuper()) { writeThis(ide); return; } if (!ide.isThis()) { IdeDeclaration decl = ide.getDeclaration(false); if (decl != null) { if (decl.isClassMember()) { if (!decl.isPrivateStatic()) { if (decl.isStatic()) { out.writeToken(decl.getClassDeclaration().getQualifiedNameStr()); } else { if (ide.isBound()) { writeBoundMethodAccess(ide, null, null, decl); return; } writeThis(ide); } } writeMemberAccess(decl, null, ide, false); return; } if (ide.getPackagePrefix().length() > 0) { out.writeToken(ide.getPackagePrefix()); } } } // take care of reserved words called as functions (Rhino does not like): if (SyntacticKeywords.RESERVED_WORDS.contains(ide.getIde().getText())) { out.writeToken("$$" + ide.getIde().getText()); } else { out.writeSymbol(ide.getIde(), false); } } private void writeBoundMethodAccess(Ide ide, Ide optIde, JooSymbol optSymDot, IdeDeclaration decl) throws IOException { out.writeToken("$$bound("); if (optIde != null) { optIde.visit(this); } else { writeThis(ide); } if (optSymDot != null) { out.writeSymbolWhitespace(optSymDot); } out.writeToken(","); out.beginString(); if (ide.usePrivateMemberName(decl)) { out.writeToken(ide.getName() + "$" + ide.getScope().getClassDeclaration().getInheritanceLevel()); } else { out.writeToken(ide.getName()); } out.endString(); out.writeToken(")"); } @Override public void visitDotExpr(DotExpr dotExpr) throws IOException { dotExpr.getArg().visit(this); writeMemberAccess(Ide.resolveMember(dotExpr.getArg().getType(), dotExpr.getIde()), dotExpr.getOp(), dotExpr.getIde(), true); } private void writeMemberAccess(IdeDeclaration memberDeclaration, JooSymbol optSymDot, Ide memberIde, boolean writeMemberWhitespace) throws IOException { if (memberDeclaration != null) { if (memberIde.usePrivateMemberName(memberDeclaration)) { writePrivateMemberAccess(optSymDot, memberIde, writeMemberWhitespace, memberDeclaration.isStatic()); return; } } if (optSymDot == null && memberDeclaration != null && !memberDeclaration.isConstructor()) { optSymDot = new JooSymbol("."); } boolean quote = false; if (optSymDot != null) { if (memberIde.getIde().getText().startsWith("@")) { quote = true; out.writeSymbolWhitespace(optSymDot); out.writeToken("['"); } else { out.writeSymbol(optSymDot); } } out.writeSymbol(memberIde.getIde(), writeMemberWhitespace); if (quote) { out.writeToken("']"); } } private void writePrivateMemberAccess(final JooSymbol optSymDot, Ide memberIde, boolean writeMemberWhitespace, boolean isStatic) throws IOException { if (writeMemberWhitespace) { out.writeSymbolWhitespace(memberIde.getIde()); } if (isStatic) { out.writeToken("$$private"); if (optSymDot != null) { out.writeSymbol(optSymDot); } else { out.writeToken("."); } out.writeSymbol(memberIde.getIde(), false); } else { if (optSymDot != null) { out.writeSymbol(optSymDot); } else { out.writeToken("."); } // awkward, but we have to be careful if we add characters to tokens: out.writeToken(memberIde.getName() + "$" + memberIde.getScope().getClassDeclaration().getInheritanceLevel()); } } @Override public void visitTypeRelation(TypeRelation typeRelation) throws IOException { out.beginCommentWriteSymbol(typeRelation.getSymRelation()); typeRelation.getType().getIde().visit(this); out.endComment(); } @Override public void visitAnnotationParameter(AnnotationParameter annotationParameter) throws IOException { visitIfNotNull(annotationParameter.getOptName(), "$value"); writeSymbolReplacement(annotationParameter.getOptSymEq(), ":"); visitIfNotNull(annotationParameter.getValue(), "null"); } @Override public void visitExtends(Extends anExtends) throws IOException { out.writeSymbol(anExtends.getSymExtends()); writeQName(anExtends.getSuperClass()); } private void writeQName(Ide classIde) throws IOException { out.writeSymbolWhitespace(classIde.getSymbol()); String classQName = classIde.getDeclaration().getQualifiedNameStr(); out.writeToken(classQName); } @Override public void visitInitializer(Initializer initializer) throws IOException { out.writeSymbol(initializer.getSymEq()); initializer.getValue().visit(this); } @Override public void visitObjectField(ObjectField objectField) throws IOException { objectField.getLabel().visit(this); out.writeSymbol(objectField.getSymColon()); objectField.getValue().visit(this); } @Override public void visitForInitializer(ForInitializer forInitializer) throws IOException { if (forInitializer.getDecl() != null) { forInitializer.getDecl().visit(this); } else { visitIfNotNull(forInitializer.getExpr()); } } @Override public void visitCompilationUnit(CompilationUnit compilationUnit) throws IOException { this.compilationUnit = compilationUnit; out.write(Jooc.CLASS_LOADER_FULLY_QUALIFIED_NAME + ".prepare("); compilationUnit.getPackageDeclaration().visit(this); out.beginComment(); out.writeSymbol(compilationUnit.getLBrace()); out.endComment(); visitAll(compilationUnit.getDirectives()); compilationUnit.getPrimaryDeclaration().visit(this); out.write(",["); boolean first = true; for (String qname : compilationUnit.getDependencies()) { if (first) { first = false; } else { out.write(","); } out.write('"' + qname + '"'); } out.write("]"); out.write(", \"" + JoocProperties.getRuntimeVersion() + "\""); out.write(", \"" + JoocProperties.getVersion() + "\""); out.writeSymbolWhitespace(compilationUnit.getRBrace()); out.write(");"); if (compilationUnit.isHasStaticCode()) { out.write("/* HAS_STATIC_CODE */"); } } @Override public void visitIde(Ide ide) throws IOException { if (expressionMode) { generateIdeCodeAsExpr(ide); return; } out.writeSymbolWhitespace(ide.getIde()); // take care of reserved words called as functions (Rhino does not like): if (!out.isWritingComment() && SyntacticKeywords.RESERVED_WORDS.contains(ide.getIde().getText())) { out.writeToken("$$" + ide.getIde().getText()); } else { out.writeSymbol(ide.getIde(), false); } } private void generateQualifiedIdeCodeAsExpr(QualifiedIde qualifiedIde) throws IOException { boolean commentOutQualifierCode = false; IdeDeclaration memberDeclaration = null; IdeDeclaration qualifierDeclaration = qualifiedIde.getQualifier().getDeclaration(false); if (qualifierDeclaration != null && qualifierDeclaration.isConstructor()) { qualifierDeclaration = qualifierDeclaration.getClassDeclaration(); } if (qualifierDeclaration != null && qualifierDeclaration.equals(qualifiedIde.getScope().getClassDeclaration())) { memberDeclaration = ((ClassDeclaration) qualifierDeclaration).getStaticMemberDeclaration(qualifiedIde.getName()); commentOutQualifierCode = memberDeclaration != null && memberDeclaration.isPrivateStatic(); } if (memberDeclaration == null) { final IdeDeclaration type = qualifiedIde.getQualifier().resolveDeclaration(); memberDeclaration = Ide.resolveMember(type, qualifiedIde); } if (qualifiedIde.isBound()) { writeBoundMethodAccess(qualifiedIde, qualifiedIde.getQualifier(), qualifiedIde.getSymDot(), memberDeclaration); return; } if (commentOutQualifierCode) { // we will generate another qualifier in writeMemberAccess out.beginComment(); } qualifiedIde.getQualifier().visit(this); if (commentOutQualifierCode) { out.endComment(); } writeMemberAccess(memberDeclaration, qualifiedIde.getSymDot(), qualifiedIde, true); } @Override public void visitQualifiedIde(QualifiedIde qualifiedIde) throws IOException { if (expressionMode) { generateQualifiedIdeCodeAsExpr(qualifiedIde); return; } qualifiedIde.getQualifier().visit(this); out.writeSymbol(qualifiedIde.getSymDot()); visitIde(qualifiedIde); } @Override public void visitIdeWithTypeParam(IdeWithTypeParam ideWithTypeParam) throws IOException { visitIde(ideWithTypeParam); out.beginComment(); out.writeSymbol(ideWithTypeParam.getSymDotLt()); ideWithTypeParam.getType().visit(this); out.writeSymbol(ideWithTypeParam.getSymGt()); out.endComment(); } @Override public void visitNamespacedIde(NamespacedIde namespacedIde) throws IOException { // so far, namespaces are only comments: out.beginComment(); out.writeSymbol(namespacedIde.getNamespace().getSymbol()); out.writeSymbol(namespacedIde.getSymNamespaceSep()); out.endComment(); visitIde(namespacedIde); } @Override public void visitImplements(Implements anImplements) throws IOException { out.writeSymbol(anImplements.getSymImplements()); generateImplements(anImplements.getSuperTypes()); } private void generateImplements(CommaSeparatedList<Ide> superTypes) throws IOException { writeQName(superTypes.getHead()); if (superTypes.getSymComma() != null) { out.writeSymbol(superTypes.getSymComma()); generateImplements(superTypes.getTail()); } } @Override public void visitType(Type type) throws IOException { type.getIde().visit(this); } @Override public void visitObjectLiteral(ObjectLiteral objectLiteral) throws IOException { out.writeSymbol(objectLiteral.getLBrace()); visitIfNotNull(objectLiteral.getFields()); writeOptSymbol(objectLiteral.getOptComma()); out.writeSymbol(objectLiteral.getRBrace()); } @Override public void visitIdeExpression(IdeExpr ideExpr) throws IOException { expressionMode = true; try { ideExpr.getIde().visit(this); } finally { expressionMode = false; } } @Override public <T extends Expr> void visitParenthesizedExpr(ParenthesizedExpr<T> parenthesizedExpr) throws IOException { out.writeSymbol(parenthesizedExpr.getLParen()); visitIfNotNull(parenthesizedExpr.getExpr()); out.writeSymbol(parenthesizedExpr.getRParen()); } @Override public void visitArrayLiteral(ArrayLiteral arrayLiteral) throws IOException { visitParenthesizedExpr(arrayLiteral); } @Override public void visitAssignmentOpExpr(AssignmentOpExpr assignmentOpExpr) throws IOException { if (assignmentOpExpr.getOp().sym == sym.ANDANDEQ || assignmentOpExpr.getOp().sym == sym.OROREQ) { assignmentOpExpr.getArg1().visit(this); out.writeSymbolWhitespace(assignmentOpExpr.getOp()); out.writeToken("="); // TODO: refactor for a simpler way to switch off white-space temporarily: JoocConfiguration options = (JoocConfiguration) out.getOptions(); DebugMode mode = options.getDebugMode(); options.setDebugMode(null); assignmentOpExpr.getArg1().visit(this); options.setDebugMode(mode); out.writeToken(assignmentOpExpr.getOp().sym == sym.ANDANDEQ ? "&&" : "||"); out.writeToken("("); assignmentOpExpr.getArg2().visit(this); out.writeToken(")"); } else { visitBinaryOpExpr(assignmentOpExpr); } } @Override public void visitInfixOpExpr(InfixOpExpr infixOpExpr) throws IOException { out.writeSymbolToken(infixOpExpr.getOp()); out.write('('); infixOpExpr.getArg1().visit(this); out.write(','); out.writeSymbolWhitespace(infixOpExpr.getOp()); infixOpExpr.getArg2().visit(this); out.write(')'); } @Override public void visitAsExpr(AsExpr asExpr) throws IOException { visitInfixOpExpr(asExpr); } @Override public void visitArrayIndexExpr(ArrayIndexExpr arrayIndexExpr) throws IOException { arrayIndexExpr.getArray().visit(this); arrayIndexExpr.getIndexExpr().visit(this); } @Override public void visitParameters(Parameters parameters) throws IOException { visitIfNotNull(parameters.getHead()); if (parameters.getSymComma() != null) { if (parameters.getTail().getHead().isRest()) { out.beginCommentWriteSymbol(parameters.getSymComma()); parameters.getTail().visit(this); out.endComment(); } else { out.writeSymbol(parameters.getSymComma()); parameters.getTail().visit(this); } } } @Override public void visitFunctionExpr(FunctionExpr functionExpr) throws IOException { out.writeSymbol(functionExpr.getSymFunction()); if (functionExpr.getIde() != null) { out.writeToken(functionExpr.getIde().getName()); } else if (out.getKeepSource()) { out.writeToken(getFunctionNameAsIde(functionExpr)); } generateFunTailCode(functionExpr); } public String getFunctionNameAsIde(FunctionExpr functionExpr) { IdeDeclaration classDeclaration = functionExpr.getClassDeclaration(); String classNameAsIde = ""; if (classDeclaration != null) { classNameAsIde = out.getQualifiedNameAsIde(classDeclaration); } JooSymbol sym = functionExpr.getSymbol(); return classNameAsIde + "$" + sym.getLine() + "_" + sym.getColumn(); } public void generateFunTailCode(FunctionExpr functionExpr) throws IOException { Parameters params = functionExpr.getParams(); if (functionExpr.hasBody()) { if (functionExpr.isArgumentsUsedAsArray()) { functionExpr.getBody().addBlockStartCodeGenerator(ARGUMENT_TO_ARRAY_CODE_GENERATOR); } if (params != null) { // inject into body for generating initializers later: functionExpr.getBody().addBlockStartCodeGenerator(getParameterInitializerCodeGenerator(params)); } } generateSignatureJsCode(functionExpr); if (functionExpr.hasBody()) { functionExpr.getBody().visit(this); } } public CodeGenerator getParameterInitializerCodeGenerator(final Parameters params) { return new CodeGenerator() { @Override public void generate(JsWriter out, boolean first) throws IOException { // collect the ... (rest) parameter and all optional parameters with their position index: int restParamIndex = -1; Parameter restParam = null; Map<Integer,Parameter> paramByIndex = new HashMap<Integer, Parameter>(); Parameters parameters = params; for (int paramIndex = 0; parameters != null; parameters = parameters.getTail()) { Parameter param = parameters.getHead(); if (param.isRest()) { restParamIndex = paramIndex; restParam = param; break; } if (param.hasInitializer()) { paramByIndex.put(paramIndex, param); } ++paramIndex; } generateParameterInitializers(out, paramByIndex); if (restParam != null) { generateRestParamCode(restParam, restParamIndex); } } }; } private final MessageFormat IF_ARGUMENT_LENGTH_LTE_$N = new MessageFormat("if(arguments.length<={0})"); private final MessageFormat SWITCH_$INDEX = new MessageFormat("switch({0,choice,0#arguments.length|0<Math.max(arguments.length,{0})})"); private final MessageFormat CASE_$N = new MessageFormat("case {0}:"); private void generateParameterInitializers(JsWriter out, Map<Integer, Parameter> paramByIndex) throws IOException { Iterator<Map.Entry<Integer, Parameter>> paramByIndexIterator = paramByIndex.entrySet().iterator(); if (paramByIndexIterator.hasNext()) { Map.Entry<Integer, Parameter> indexAndParam = paramByIndexIterator.next(); Integer firstParamIndex = indexAndParam.getKey(); if (!paramByIndexIterator.hasNext()) { // only one parameter initializer: use "if" out.write(IF_ARGUMENT_LENGTH_LTE_$N.format(firstParamIndex)); generateBodyInitializerCode(indexAndParam.getValue()); } else { // more than one parameter initializer: use "switch" out.write(SWITCH_$INDEX.format(firstParamIndex)); out.write("{"); while (true) { out.write(CASE_$N.format(indexAndParam.getKey())); generateBodyInitializerCode(indexAndParam.getValue()); if (!paramByIndexIterator.hasNext()) { break; } indexAndParam = paramByIndexIterator.next(); } out.write("}"); } } } public void generateRestParamCode(Parameter param, int paramIndex) throws IOException { String paramName = param.getName(); if (paramName != null && !(paramName.equals(FunctionExpr.ARGUMENTS) && paramIndex == 0)) { generateToArrayCode(paramName, paramIndex); } } public void generateBodyInitializerCode(Parameter param) throws IOException { out.setSuppressWhitespace(true); // do not output whitespace twice! try { out.writeToken(param.getName()); out.writeSymbol(param.getOptInitializer().getSymEq()); param.getOptInitializer().getValue().visit(this); out.write(";"); } finally { out.setSuppressWhitespace(false); } } public void generateSignatureJsCode(FunctionExpr functionExpr) throws IOException { out.writeSymbol(functionExpr.getLParen()); visitIfNotNull(functionExpr.getParams()); out.writeSymbol(functionExpr.getRParen()); visitIfNotNull(functionExpr.getOptTypeRelation()); } @Override public void visitVectorLiteral(VectorLiteral vectorLiteral) throws IOException { out.beginComment(); out.writeSymbol(vectorLiteral.getSymNew()); out.writeSymbol(vectorLiteral.getSymLt()); vectorLiteral.getVectorType().visit(this); out.writeSymbol(vectorLiteral.getSymGt()); out.endComment(); vectorLiteral.getArrayLiteral().visit(this); } @Override public void visitApplyExpr(ApplyExpr applyExpr) throws IOException { generateFunJsCode(applyExpr); if (applyExpr.getArgs() != null) { boolean isAssert = applyExpr.getFun() instanceof IdeExpr && SyntacticKeywords.ASSERT.equals(applyExpr.getFun().getSymbol().getText()); if (isAssert) { JooSymbol symKeyword = applyExpr.getFun().getSymbol(); out.writeSymbol(applyExpr.getArgs().getLParen()); applyExpr.getArgs().getExpr().visit(this); out.writeToken(", "); out.writeString(new File(symKeyword.getFileName()).getName()); out.writeToken(", "); out.writeInt(symKeyword.getLine()); out.write(", "); out.writeInt(symKeyword.getColumn()); out.writeSymbol(applyExpr.getArgs().getRParen()); } else { applyExpr.getArgs().visit(this); } } } private void generateFunJsCode(ApplyExpr applyExpr) throws IOException { // leave out constructor function if called as type cast function! // these old-style type casts are soo ugly.... if (applyExpr.isTypeCast()) { out.beginComment(); applyExpr.getFun().visit(this); out.endComment(); } else { applyExpr.getFun().visit(this); } } @Override public void visitNewExpr(NewExpr newExpr) throws IOException { out.writeSymbol(newExpr.getSymNew()); newExpr.getApplyConstructor().visit(this); } @Override public void visitClassBody(ClassBody classBody) throws IOException { out.writeSymbolWhitespace(classBody.getLBrace()); boolean inStaticInitializerBlock = false; for (Directive directive : classBody.getDirectives()) { final boolean isStaticInitializer = directive instanceof Statement && !(directive instanceof Declaration); if (isStaticInitializer) { inStaticInitializerBlock = beginStaticInitializer(out, inStaticInitializerBlock); } else { inStaticInitializerBlock = endStaticInitializer(out, inStaticInitializerBlock); } directive.visit(this); } endStaticInitializer(out, inStaticInitializerBlock); out.writeSymbolWhitespace(classBody.getRBrace()); } private boolean beginStaticInitializer(JsWriter out, boolean inStaticInitializerBlock) throws IOException { if (!inStaticInitializerBlock) { out.writeToken("function(){"); } return true; } private boolean endStaticInitializer(JsWriter out, boolean inStaticInitializerBlock) throws IOException { if (inStaticInitializerBlock) { out.writeToken("},"); } return false; } @Override public void visitBlockStatement(BlockStatement blockStatement) throws IOException { out.writeSymbol(blockStatement.getLBrace()); boolean first = true; for (CodeGenerator codeGenerator : blockStatement.getBlockStartCodeGenerators()) { codeGenerator.generate(out, first); first = false; } visitAll(blockStatement.getDirectives()); out.writeSymbol(blockStatement.getRBrace()); } @Override public void visitDefaultStatement(DefaultStatement defaultStatement) throws IOException { out.writeSymbol(defaultStatement.getSymDefault()); out.writeSymbol(defaultStatement.getSymColon()); } @Override public void visitLabeledStatement(LabeledStatement labeledStatement) throws IOException { labeledStatement.getIde().visit(this); out.writeSymbol(labeledStatement.getSymColon()); labeledStatement.getStatement().visit(this); } @Override public void visitIfStatement(IfStatement ifStatement) throws IOException { out.writeSymbol(ifStatement.getSymKeyword()); ifStatement.getCond().visit(this); ifStatement.getIfTrue().visit(this); if (ifStatement.getSymElse() != null) { out.writeSymbol(ifStatement.getSymElse()); ifStatement.getIfFalse().visit(this); } } @Override public void visitCaseStatement(CaseStatement caseStatement) throws IOException { out.writeSymbol(caseStatement.getSymKeyword()); caseStatement.getExpr().visit(this); out.writeSymbol(caseStatement.getSymColon()); } @Override public void visitTryStatement(TryStatement tryStatement) throws IOException { out.writeSymbol(tryStatement.getSymKeyword()); tryStatement.getBlock().visit(this); visitAll(tryStatement.getCatches()); if (tryStatement.getSymFinally() != null) { out.writeSymbol(tryStatement.getSymFinally()); tryStatement.getFinallyBlock().visit(this); } //To change body of implemented methods use File | Settings | File Templates. } @Override public void visitCatch(Catch aCatch) throws IOException { List<Catch> catches = aCatch.getParentTryStatement().getCatches(); Catch firstCatch = catches.get(0); boolean isFirst = aCatch.equals(firstCatch); boolean isLast = aCatch.equals(catches.get(catches.size() - 1)); TypeRelation typeRelation = aCatch.getParam().getOptTypeRelation(); boolean hasCondition = aCatch.hasCondition(); if (!hasCondition && !isLast) { throw Jooc.error(aCatch.getRParen(), "Only last catch clause may be untyped."); } final JooSymbol errorVar = firstCatch.getParam().getIde().getIde(); final JooSymbol localErrorVar = aCatch.getParam().getIde().getIde(); // in the following, always take care to write whitespace only once! out.writeSymbolWhitespace(aCatch.getSymKeyword()); if (isFirst) { out.writeSymbolToken(aCatch.getSymKeyword()); // "catch" // "(localErrorVar)": out.writeSymbol(aCatch.getLParen(), !hasCondition); out.writeSymbol(errorVar, !hasCondition); if (!hasCondition && typeRelation != null) { // can only be ": *", add as comment: typeRelation.visit(this); } out.writeSymbol(aCatch.getRParen(), !hasCondition); if (hasCondition || !isLast) { // a catch block always needs a brace, so generate one for conditions: out.writeToken("{"); } } else { // transform catch(ide:Type){...} into else if is(e,Type)){var ide=e;...} out.writeToken("else"); } if (hasCondition) { out.writeToken("if(is"); out.writeSymbol(aCatch.getLParen()); out.writeSymbolWhitespace(localErrorVar); out.writeSymbolToken(errorVar); out.writeSymbolWhitespace(typeRelation.getSymRelation()); out.writeToken(","); Ide typeIde = typeRelation.getType().getIde(); out.writeSymbolWhitespace(typeIde.getIde()); out.writeToken(typeIde.getDeclaration().getQualifiedNameStr()); out.writeSymbol(aCatch.getRParen()); out.writeToken(")"); } if (!localErrorVar.getText().equals(errorVar.getText())) { aCatch.getBlock().addBlockStartCodeGenerator(new VarCodeGenerator(localErrorVar, errorVar)); } aCatch.getBlock().visit(this); if (isLast) { if (hasCondition) { out.writeToken("else throw"); out.writeSymbolToken(errorVar); out.writeToken(";"); } if (!(isFirst && !hasCondition)) { // last catch clause closes the JS catch block: out.writeToken("}"); } } } private static class VarCodeGenerator implements CodeGenerator { private final JooSymbol localErrorVar; private final JooSymbol errorVar; public VarCodeGenerator(JooSymbol localErrorVar, JooSymbol errorVar) { this.localErrorVar = localErrorVar; this.errorVar = errorVar; } @Override public void generate(JsWriter out, boolean first) throws IOException { out.writeToken("var"); out.writeSymbolToken(localErrorVar); out.writeToken("="); out.writeSymbolToken(errorVar); out.writeToken(";"); } } @Override public void visitForInStatement(final ForInStatement forInStatement) throws IOException { final Ide exprAuxIde = forInStatement.getExprAuxIde(); IdeDeclaration exprType = forInStatement.getExpr().getType(); String exprTypeName = exprType != null ? exprType.getQualifiedNameStr() : ""; boolean iterateArrayMode = "Array".equals(exprTypeName) || "Vector$object".equals(exprTypeName); if (exprAuxIde != null && !iterateArrayMode) { new SemicolonTerminatedStatement(new VariableDeclaration(SYM_VAR, exprAuxIde, null, null), SYM_SEMICOLON).visit(this); } out.writeSymbol(forInStatement.getSymKeyword()); final boolean isForEach = forInStatement.getSymEach() != null; if (isForEach) { out.beginComment(); out.writeSymbol(forInStatement.getSymEach()); out.endComment(); } out.writeSymbol(forInStatement.getLParen()); if (isForEach || iterateArrayMode) { new VariableDeclaration(SYM_VAR, forInStatement.getAuxIde(), null, null).visit(this); } else { if (forInStatement.getDecl() != null) { forInStatement.getDecl().visit(this); } else { forInStatement.getLValue().visit(this); } } if (iterateArrayMode) { String indexVarName = forInStatement.getAuxIde().getName(); out.write("=0"); if (exprAuxIde != null) { out.write(","); out.writeToken(exprAuxIde.getName()); out.writeToken("="); out.beginComment(); out.writeSymbol(forInStatement.getSymIn()); out.endComment(); forInStatement.getExpr().visit(this); } out.write(";"); out.write(indexVarName); out.write("<"); if (exprAuxIde != null) { out.writeToken(exprAuxIde.getName()); } else { out.beginComment(); out.writeSymbol(forInStatement.getSymIn()); out.endComment(); forInStatement.getExpr().visit(this); } out.write(".length;"); out.write("++" + indexVarName); } else { out.writeSymbol(forInStatement.getSymIn()); if (exprAuxIde != null) { // assign the expression value to the auxiliary expression value variable once: out.writeToken(exprAuxIde.getName()); out.writeToken("="); } forInStatement.getExpr().visit(this); } out.writeSymbol(forInStatement.getRParen()); if (isForEach || iterateArrayMode) { // inject synthesized statement into loop body: if (!(forInStatement.getBody() instanceof BlockStatement)) { forInStatement.setBody(new BlockStatement(SYM_LBRACE, Arrays.<Directive>asList(forInStatement.getBody()), SYM_RBRACE)); } ((BlockStatement) forInStatement.getBody()).addBlockStartCodeGenerator(new CodeGenerator() { @Override public void generate(JsWriter out, boolean first) throws IOException { // synthesize assigning the correct index to the variable given in the original for each statement: if (forInStatement.getDecl() != null) { forInStatement.getDecl().visit(JsCodeGenerator.this); } else { forInStatement.getLValue().visit(JsCodeGenerator.this); } out.writeToken("="); if (!isForEach) { out.write("String(" + forInStatement.getAuxIde().getName() + ")"); } else { if (exprAuxIde == null) { forInStatement.getExpr().visit(JsCodeGenerator.this); } else { out.write(exprAuxIde.getName()); } out.write("[" + forInStatement.getAuxIde().getName() + "]"); } out.write(";"); } }); } forInStatement.getBody().visit(this); } @Override public void visitWhileStatement(WhileStatement whileStatement) throws IOException { out.writeSymbol(whileStatement.getSymKeyword()); visitIfNotNull(whileStatement.getOptCond()); whileStatement.getBody().visit(this); } @Override public void visitForStatement(ForStatement forStatement) throws IOException { out.writeSymbol(forStatement.getSymKeyword()); out.writeSymbol(forStatement.getLParen()); visitIfNotNull(forStatement.getForInit()); out.writeSymbol(forStatement.getSymSemicolon1()); visitIfNotNull(forStatement.getOptCond()); out.writeSymbol(forStatement.getSymSemicolon2()); visitIfNotNull(forStatement.getOptStep()); out.writeSymbol(forStatement.getRParen()); forStatement.getBody().visit(this); } @Override public void visitDoStatement(DoStatement doStatement) throws IOException { out.writeSymbol(doStatement.getSymKeyword()); doStatement.getBody().visit(this); out.writeSymbol(doStatement.getSymWhile()); doStatement.getOptCond().visit(this); out.writeSymbol(doStatement.getSymSemicolon()); } @Override public void visitSwitchStatement(SwitchStatement switchStatement) throws IOException { out.writeSymbol(switchStatement.getSymKeyword()); switchStatement.getCond().visit(this); switchStatement.getBlock().visit(this); //To change body of implemented methods use File | Settings | File Templates. } @Override public void visitSemicolonTerminatedStatement(SemicolonTerminatedStatement semicolonTerminatedStatement) throws IOException { visitIfNotNull(semicolonTerminatedStatement.getOptStatement()); writeOptSymbol(semicolonTerminatedStatement.getOptSymSemicolon()); } @Override public void visitContinueStatement(ContinueStatement continueStatement) throws IOException { out.writeSymbol(continueStatement.getSymKeyword()); visitIfNotNull(continueStatement.getOptStatement()); visitIfNotNull(continueStatement.getOptLabel()); writeOptSymbol(continueStatement.getOptSymSemicolon()); } @Override public void visitBreakStatement(BreakStatement breakStatement) throws IOException { out.writeSymbol(breakStatement.getSymKeyword()); visitIfNotNull(breakStatement.getOptStatement()); visitIfNotNull(breakStatement.getOptLabel()); writeOptSymbol(breakStatement.getOptSymSemicolon()); } @Override public void visitThrowStatement(ThrowStatement throwStatement) throws IOException { out.writeSymbol(throwStatement.getSymKeyword()); visitIfNotNull(throwStatement.getOptStatement()); writeOptSymbol(throwStatement.getOptSymSemicolon()); } @Override public void visitReturnStatement(ReturnStatement returnStatement) throws IOException { out.writeSymbol(returnStatement.getSymKeyword()); visitIfNotNull(returnStatement.getOptStatement()); writeOptSymbol(returnStatement.getOptSymSemicolon()); } @Override public void visitEmptyStatement(EmptyStatement emptyStatement) throws IOException { visitSemicolonTerminatedStatement(emptyStatement); } @Override public void visitEmptyDeclaration(EmptyDeclaration emptyDeclaration) throws IOException { out.writeSymbolWhitespace(emptyDeclaration.getSymSemicolon()); } @Override public void visitParameter(Parameter parameter) throws IOException { Debug.assertTrue(parameter.getModifiers() == 0, "Parameters must not have any modifiers"); boolean isRest = parameter.isRest(); if (parameter.getOptSymConstOrRest() != null) { out.beginCommentWriteSymbol(parameter.getOptSymConstOrRest()); if (isRest) { parameter.getIde().visit(this); } out.endComment(); } if (!isRest) { parameter.getIde().visit(this); } visitIfNotNull(parameter.getOptTypeRelation()); // in the method signature, comment out initializer code. if (parameter.getOptInitializer() != null) { out.beginComment(); parameter.getOptInitializer().visit(this); out.endComment(); } } @Override public void visitVariableDeclaration(VariableDeclaration variableDeclaration) throws IOException { if (variableDeclaration.equals(compilationUnit.getPrimaryDeclaration())) { generatePrimaryVarDeclaration(variableDeclaration); return; } if (variableDeclaration.hasPreviousVariableDeclaration()) { Debug.assertTrue(variableDeclaration.getOptSymConstOrVar() != null && variableDeclaration.getOptSymConstOrVar().sym == sym.COMMA, "Additional variable declarations must start with a COMMA."); out.writeSymbol(variableDeclaration.getOptSymConstOrVar()); } else { generateVariableDeclarationStartCode(variableDeclaration); } variableDeclaration.getIde().visit(this); visitIfNotNull(variableDeclaration.getOptTypeRelation()); generateVariableDeclarationInitializerCode(variableDeclaration); visitIfNotNull(variableDeclaration.getOptNextVariableDeclaration()); generateVariableDeclarationEndCode(variableDeclaration); } private void generatePrimaryVarDeclaration(VariableDeclaration variableDeclaration) throws IOException { out.beginString(); writeModifiers(out, variableDeclaration); out.writeSymbol(variableDeclaration.getOptSymConstOrVar()); variableDeclaration.getIde().visit(this); out.endString(); visitIfNotNull(variableDeclaration.getOptTypeRelation()); out.writeToken(",0,"); // place-holder for inheritance level if (variableDeclaration.getOptInitializer() != null) { out.write("function(){"); writeAliases(); out.writeSymbolWhitespace(variableDeclaration.getOptInitializer().getSymEq()); out.writeToken("return"); variableDeclaration.getOptInitializer().getValue().visit(this); out.write(";\n}"); } else { out.writeToken("null"); } out.write(",[]"); } protected void generateVariableDeclarationStartCode(VariableDeclaration variableDeclaration) throws IOException { if (variableDeclaration.equals(compilationUnit.getPrimaryDeclaration())) { generatePrimaryVarDeclaration(variableDeclaration); } else if (variableDeclaration.isClassMember()) { generateFieldStartCode(variableDeclaration); } else { generateVarStartCode(variableDeclaration); } } protected void generateVarStartCode(VariableDeclaration variableDeclaration) throws IOException { out.beginComment(); writeModifiers(out, variableDeclaration); out.endComment(); if (variableDeclaration.getOptSymConstOrVar() != null) { if (variableDeclaration.isConst()) { out.beginCommentWriteSymbol(variableDeclaration.getOptSymConstOrVar()); out.endComment(); out.writeToken("var"); } else { out.writeSymbol(variableDeclaration.getOptSymConstOrVar()); } } } protected void generateFieldStartCode(VariableDeclaration variableDeclaration) throws IOException { out.beginString(); writeModifiers(out, variableDeclaration); writeOptSymbol(variableDeclaration.getOptSymConstOrVar()); out.endString(); out.write(",{"); } protected void generateVariableDeclarationInitializerCode(VariableDeclaration variableDeclaration) throws IOException { if (variableDeclaration.isClassMember()) { generateFieldInitializerCode(variableDeclaration); } else { visitIfNotNull(variableDeclaration.getOptInitializer()); } } protected void generateFieldInitializerCode(VariableDeclaration variableDeclaration) throws IOException { if (variableDeclaration.getOptInitializer() != null) { out.writeSymbolWhitespace(variableDeclaration.getOptInitializer().getSymEq()); out.write(':'); boolean mustEvaluateAtRuntime = !variableDeclaration.getOptInitializer().getValue().isRuntimeConstant(); if (mustEvaluateAtRuntime) { out.writeToken("function(){return("); } variableDeclaration.getOptInitializer().getValue().visit(this); if (mustEvaluateAtRuntime) { out.writeToken(");}"); } } else { TypeRelation typeRelation = variableDeclaration.getOptTypeRelation(); String emptyValue = VariableDeclaration.getDefaultValue(typeRelation); out.write(":" + emptyValue); } } protected void generateVariableDeclarationEndCode(VariableDeclaration variableDeclaration) throws IOException { if (variableDeclaration.isClassMember()) { generateFieldEndCode(variableDeclaration); } else { writeOptSymbol(variableDeclaration.getOptSymSemicolon()); } } protected void generateFieldEndCode(VariableDeclaration variableDeclaration) throws IOException { if (!variableDeclaration.hasPreviousVariableDeclaration()) { out.write('}'); Debug.assertTrue(variableDeclaration.getOptSymSemicolon() != null, "optSymSemicolon != null"); out.writeSymbolWhitespace(variableDeclaration.getOptSymSemicolon()); out.writeToken(","); } } private static final CodeGenerator ALIAS_THIS_CODE_GENERATOR = new CodeGenerator() { @Override public void generate(JsWriter out, boolean first) throws IOException { out.write("var this$=this;"); } }; @Override public void visitFunctionDeclaration(FunctionDeclaration functionDeclaration) throws IOException { boolean isPrimaryDeclaration = functionDeclaration.equals(compilationUnit.getPrimaryDeclaration()); assert functionDeclaration.isClassMember() || (!functionDeclaration.isNative() && !functionDeclaration.isAbstract()); if (functionDeclaration.isThisAliased()) { functionDeclaration.getBody().addBlockStartCodeGenerator(ALIAS_THIS_CODE_GENERATOR); } if (functionDeclaration.isConstructor() && !functionDeclaration.containsSuperConstructorCall() && functionDeclaration.hasBody()) { functionDeclaration.getBody().addBlockStartCodeGenerator(new SuperCallCodeGenerator(functionDeclaration.getClassDeclaration())); } if (!functionDeclaration.isClassMember() && !isPrimaryDeclaration) { functionDeclaration.getFun().visit(this); } else { if (functionDeclaration.isAbstract()) { out.beginComment(); writeModifiers(out, functionDeclaration); out.writeSymbol(functionDeclaration.getFun().getFunSymbol()); functionDeclaration.getIde().visit(this); } else { out.beginString(); writeModifiers(out, functionDeclaration); out.writeSymbol(functionDeclaration.getFun().getFunSymbol()); if (functionDeclaration.isGetterOrSetter()) { out.writeSymbol(functionDeclaration.getSymGetOrSet()); } functionDeclaration.getIde().visit(this); out.endString(); if (functionDeclaration.isNative()) { out.beginComment(); } else { out.writeToken(","); if (isPrimaryDeclaration) { out.write("0,"); // place-holder for inheritance level out.write("function(){"); writeAliases(); out.writeToken("return"); } out.writeToken("function"); if (out.getKeepSource()) { String functionName = functionDeclaration.getIde().getName(); if (functionDeclaration.getSymGetOrSet() != null) { out.writeToken(functionName + "$" + functionDeclaration.getSymGetOrSet().getText()); } else { out.writeToken(functionName); } } } } generateFunTailCode(functionDeclaration.getFun()); if (functionDeclaration.isClassMember()) { if (functionDeclaration.isAbstract() || functionDeclaration.isNative()) { out.endComment(); } out.write(','); } else if (isPrimaryDeclaration) { out.write("\n;},[]"); // end initializer function and place-holder for static method names } } } @Override public void visitClassDeclaration(ClassDeclaration classDeclaration) throws IOException { out.beginString(); writeModifiers(out, classDeclaration); out.writeSymbol(classDeclaration.getSymClass()); classDeclaration.getIde().visit(this); visitIfNotNull(classDeclaration.getOptExtends()); visitIfNotNull(classDeclaration.getOptImplements()); out.endString(); out.write(","); out.write(classDeclaration.getInheritanceLevel() + ","); out.write("function($$private){"); writeAliases(); out.write("return["); generateClassInits(classDeclaration); classDeclaration.getBody().visit(this); if (classDeclaration.getConstructor() == null && !classDeclaration.getFieldsWithInitializer().isEmpty()) { // generate default constructor that calls field initializers: out.write("\"public function " + classDeclaration.getName() + "\",function " + classDeclaration.getName() + "$(){"); new SuperCallCodeGenerator(classDeclaration).generate(out, true); out.write("},"); } for (IdeDeclaration secondaryDeclaration : classDeclaration.getSecondaryDeclarations()) { secondaryDeclaration.visit(this); out.writeToken(","); } out.write("undefined];},"); generateStaticMethodList(classDeclaration); } private void generateFieldInitCode(ClassDeclaration classDeclaration, boolean startWithSemicolon) throws IOException { Iterator<VariableDeclaration> iterator = classDeclaration.getFieldsWithInitializer().iterator(); if (iterator.hasNext()) { if (startWithSemicolon) { out.write(";"); } do { generateInitCode(iterator.next()); } while (iterator.hasNext()); } } public void generateInitCode(VariableDeclaration field) throws IOException { String fieldName = field.getName(); if (field.isPrivate()) { fieldName += "$" + field.getClassDeclaration().getInheritanceLevel(); } out.write("joo.initField(this, \"" + fieldName + "\");"); } private class SuperCallCodeGenerator implements CodeGenerator { private ClassDeclaration classDeclaration; public SuperCallCodeGenerator(ClassDeclaration classDeclaration) { this.classDeclaration = classDeclaration; } @Override public void generate(JsWriter out, boolean first) throws IOException { int inheritanceLevel = classDeclaration.getInheritanceLevel(); if (inheritanceLevel > 1) { // suppress for classes extending Object generateSuperConstructorCallCode(classDeclaration, null); out.writeToken(";"); } generateFieldInitCode(classDeclaration, false); } } private void generateStaticMethodList(ClassDeclaration classDeclaration) throws IOException { out.write("["); boolean isFirst = true; for (TypedIdeDeclaration memberDeclaration : classDeclaration.getStaticMembers().values()) { if (memberDeclaration.isMethod() && !memberDeclaration.isPrivate() && !memberDeclaration.isProtected() && memberDeclaration.isStatic() && !memberDeclaration.isNative()) { if (isFirst) { isFirst = false; } else { out.write(","); } out.write('"'); out.write(memberDeclaration.getName()); out.write('"'); } } out.write("]"); } private void writeAliases() throws IOException { boolean first = true; for (Map.Entry<String,String> entry : compilationUnit.getAuxVarDeclarations().entrySet()) { if (first) { out.writeToken("var"); first = false; } else { out.writeToken(","); } out.writeToken(entry.getKey()); out.writeToken("="); out.writeToken(entry.getValue()); } if (!first) { out.writeToken(";"); } } private void generateClassInits(ClassDeclaration classDeclaration) throws IOException { boolean first = true; Set<String> classInit = classDeclaration.getClassInit(); for (String qualifiedNameStr : classInit) { if (!PRIMITIVES.contains(qualifiedNameStr)) { if (first) { first = false; out.write("function(){" + Jooc.CLASS_LOADER_FULLY_QUALIFIED_NAME + ".init("); } else { out.write(","); } out.write(qualifiedNameStr); } } if (!first) { out.write(");},"); } } @Override public void visitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) throws IOException { out.beginString(); writeModifiers(out, namespaceDeclaration); out.writeSymbol(namespaceDeclaration.getSymNamespace()); namespaceDeclaration.getIde().visit(this); out.endString(); out.writeSymbolWhitespace(namespaceDeclaration.getOptInitializer().getSymEq()); out.writeToken(","); namespaceDeclaration.getOptInitializer().getValue().visit(this); writeSymbolReplacement(namespaceDeclaration.getOptSymSemicolon(), ",[]"); } @Override public void visitPackageDeclaration(PackageDeclaration packageDeclaration) throws IOException { out.beginString(); out.writeSymbol(packageDeclaration.getSymPackage()); visitIfNotNull(packageDeclaration.getIde()); out.endString(); out.write(","); } @Override public void visitSuperConstructorCallStatement(SuperConstructorCallStatement superConstructorCallStatement) throws IOException { if (superConstructorCallStatement.getClassDeclaration().getInheritanceLevel() > 1) { generateSuperConstructorCallCode(superConstructorCallStatement); generateFieldInitCode(superConstructorCallStatement.getClassDeclaration(), true); } else { // suppress for classes extending Object // Object super call does nothing anyway: out.beginComment(); out.writeSymbol(superConstructorCallStatement.getSymbol()); visitIfNotNull(superConstructorCallStatement.getArgs()); out.endComment(); generateFieldInitCode(superConstructorCallStatement.getClassDeclaration(), false); } out.writeSymbol(superConstructorCallStatement.getSymSemicolon()); } private void generateSuperConstructorCallCode(SuperConstructorCallStatement superConstructorCallStatement) throws IOException { out.writeSymbolWhitespace(superConstructorCallStatement.getSymbol()); generateSuperConstructorCallCode(superConstructorCallStatement.getClassDeclaration(), superConstructorCallStatement.getArgs()); } private void generateSuperConstructorCallCode(ClassDeclaration classDeclaration, ParenthesizedExpr<CommaSeparatedList<Expr>> args) throws IOException { String superClassQName = classDeclaration.getSuperTypeDeclaration().getQualifiedNameStr(); if ("Error".equals(superClassQName)) { // built-in Error constructor called as function unfortunately always creates a new Error object, so we have to use emulation provided by Jangaroo Runtime: out.write("joo.Error"); } else { Ide superClassIde = classDeclaration.getSuperType().getIde(); out.writeSymbolWhitespace(superClassIde.getSymbol()); IdeDeclaration superClassDeclaration = superClassIde.getDeclaration(); String packageName = superClassDeclaration.getPackageDeclaration().getQualifiedNameStr(); String qName = superClassDeclaration.getName(); if (packageName.length() > 0) { String packageAuxVar = compilationUnit.getAuxVarForPackage(packageName); qName = CompilerUtils.qName(packageAuxVar, qName); } out.write(qName); } out.writeToken(".call"); if (args == null) { out.writeToken("(this)"); } else { out.writeSymbol(args.getLParen()); out.writeToken("this"); CommaSeparatedList<Expr> arguments = args.getExpr(); if (arguments != null) { if (arguments.getHead() != null) { out.writeToken(","); } arguments.visit(this); } out.writeSymbol(args.getRParen()); } } @Override public void visitAnnotation(Annotation annotation) throws IOException { out.writeSymbolWhitespace(annotation.getLeftBracket()); out.writeToken("{"); annotation.getIde().visit(this); out.writeToken(":"); writeSymbolReplacement(annotation.getOptLeftParen(), "{"); visitIfNotNull(annotation.getOptAnnotationParameters()); writeSymbolReplacement(annotation.getOptRightParen(), "}"); out.writeSymbolWhitespace(annotation.getRightBracket()); out.writeToken("},"); } @Override public void visitUseNamespaceDirective(UseNamespaceDirective useNamespaceDirective) throws IOException { out.beginComment(); out.writeSymbol(useNamespaceDirective.getUseKeyword()); out.writeSymbol(useNamespaceDirective.getNamespaceKeyword()); useNamespaceDirective.getNamespace().visit(this); out.writeSymbol(useNamespaceDirective.getSymSemicolon()); out.endComment(); } @Override public void visitImportDirective(ImportDirective importDirective) throws IOException { if (importDirective.isExplicit()) { out.beginComment(); out.writeSymbol(importDirective.getImportKeyword()); importDirective.getIde().visit(this); out.writeSymbol(importDirective.getSymSemicolon()); out.endComment(); } } }