/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.codegen.js.amd; import java.io.FilterWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.flex.compiler.codegen.js.amd.IJSAMDDocEmitter; import org.apache.flex.compiler.codegen.js.amd.IJSAMDEmitter; import org.apache.flex.compiler.definitions.IAccessorDefinition; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IConstantDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition; import org.apache.flex.compiler.definitions.IInterfaceDefinition; import org.apache.flex.compiler.definitions.IPackageDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.IVariableDefinition; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.flex.compiler.internal.codegen.js.JSEmitter; import org.apache.flex.compiler.internal.codegen.js.JSEmitterTokens; import org.apache.flex.compiler.internal.codegen.js.utils.EmitterUtils; import org.apache.flex.compiler.internal.definitions.ClassTraitsDefinition; import org.apache.flex.compiler.internal.tree.as.FunctionCallNode; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.IdentifierNode; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.tree.ASTNodeID; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IAccessorNode; import org.apache.flex.compiler.tree.as.IBlockNode; import org.apache.flex.compiler.tree.as.IClassNode; import org.apache.flex.compiler.tree.as.IContainerNode; import org.apache.flex.compiler.tree.as.IDefinitionNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.IFunctionCallNode; import org.apache.flex.compiler.tree.as.IFunctionNode; import org.apache.flex.compiler.tree.as.IGetterNode; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.as.IInterfaceNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode; import org.apache.flex.compiler.tree.as.IMemberAccessExpressionNode; import org.apache.flex.compiler.tree.as.IParameterNode; import org.apache.flex.compiler.tree.as.ISetterNode; import org.apache.flex.compiler.tree.as.ITypeNode; import org.apache.flex.compiler.tree.as.IVariableNode; import org.apache.flex.compiler.utils.NativeUtils; /** * Concrete implementation of the 'AMD' JavaScript production. * * @author Michael Schmalle */ public class JSAMDEmitter extends JSEmitter implements IJSAMDEmitter { private Map<String, IDefinitionNode> foundAccessors = new HashMap<String, IDefinitionNode>(); private int inheritenceLevel = -1; private ExportWriter exportWriter; private boolean initializingFieldsInConstructor; private List<IDefinition> baseClassCalls = new ArrayList<IDefinition>(); StringBuilder builder() { return getBuilder(); } IJSAMDDocEmitter getDoc() { return (IJSAMDDocEmitter) getDocEmitter(); } public JSAMDEmitter(FilterWriter out) { super(out); exportWriter = new ExportWriter(this); } @Override public void emitPackageHeader(IPackageDefinition definition) { // TODO (mschmalle|AMD) this is a hack but I know no other way to do replacements in a Writer setBufferWrite(true); write(JSAMDEmitterTokens.DEFINE); write(ASEmitterTokens.PAREN_OPEN); IASScope containedScope = definition.getContainedScope(); ITypeDefinition type = findType(containedScope.getAllLocalDefinitions()); if (type == null) return; exportWriter.addFrameworkDependencies(); exportWriter.addImports(type); exportWriter.queueExports(type, true); writeToken(ASEmitterTokens.COMMA); } @Override public void emitPackageHeaderContents(IPackageDefinition definition) { // nothing } @Override public void emitPackageContents(IPackageDefinition definition) { IASScope containedScope = definition.getContainedScope(); ITypeDefinition type = findType(containedScope.getAllLocalDefinitions()); if (type == null) return; write("function($exports"); exportWriter.queueExports(type, false); write(") {"); indentPush(); writeNewline(); write("\"use strict\"; "); writeNewline(); ITypeNode tnode = findTypeNode(definition.getNode()); if (tnode != null) { getWalker().walk(tnode); // IClassNode | IInterfaceNode } indentPop(); writeNewline(); write("}"); // end returned function } @Override public void emitPackageFooter(IPackageDefinition definition) { IASScope containedScope = definition.getContainedScope(); ITypeDefinition type = findType(containedScope.getAllLocalDefinitions()); if (type == null) return; exportWriter.writeExports(type, true); exportWriter.writeExports(type, false); write(");"); // end define() // flush the buffer, writes the builder to out flushBuilder(); } private void emitConstructor(IFunctionNode node) { FunctionNode fn = (FunctionNode) node; fn.parseFunctionBody(getProblems()); //IFunctionDefinition definition = node.getDefinition(); write("function "); write(node.getName()); emitParameters(node.getParametersContainerNode()); if (!isImplicit((IContainerNode) node.getScopedNode())) { emitMethodScope(node.getScopedNode()); } else { // we have a synthesized constructor, implict } } @Override public void emitInterface(IInterfaceNode node) { final IInterfaceDefinition definition = node.getDefinition(); final String interfaceName = definition.getBaseName(); write("AS3.interface_($exports, {"); indentPush(); writeNewline(); write("package_: \""); write(definition.getPackageName()); write("\","); writeNewline(); write("interface_: \""); write(interfaceName); write("\""); IReference[] references = definition.getExtendedInterfaceReferences(); final int len = references.length; if (len > 0) { writeNewline(); write("extends_: ["); indentPush(); writeNewline(); int i = 0; for (IReference reference : references) { write(reference.getName()); if (i < len - 1) { write(","); writeNewline(); } i++; } indentPop(); writeNewline(); write("]"); } indentPop(); writeNewline(); write("});"); // end compilation unit } @Override public void emitClass(IClassNode node) { //ICompilerProject project = getWalker().getProject(); IClassDefinition definition = node.getDefinition(); getModel().setCurrentClass(definition); final String className = definition.getBaseName(); write("AS3.compilationUnit($exports, function($primaryDeclaration){"); indentPush(); writeNewline(); // write constructor emitConstructor((IFunctionNode) definition.getConstructor().getNode()); writeNewline(); // base class IReference baseClassReference = definition.getBaseClassReference(); boolean hasSuper = baseClassReference != null && !baseClassReference.getName().equals("Object"); if (hasSuper) { String baseName = baseClassReference.getName(); write("var Super = (" + baseName + "._ || " + baseName + "._$get());"); writeNewline(); write("var super$ = Super.prototype;"); writeNewline(); } write("$primaryDeclaration(AS3.class_({"); indentPush(); writeNewline(); // write out package write("package_: \"" + definition.getPackageName() + "\","); writeNewline(); // write class write("class_: \"" + definition.getBaseName() + "\","); writeNewline(); if (hasSuper) { write("extends_: Super,"); writeNewline(); } IReference[] references = definition .getImplementedInterfaceReferences(); int len = references.length; // write implements write("implements_:"); write(" ["); if (len > 0) { indentPush(); writeNewline(); } int i = 0; for (IReference reference : references) { write(reference.getName()); exportWriter.addDependency(reference.getName(), reference.getDisplayString(), false, false); if (i < len - 1) { write(","); writeNewline(); } i++; } if (len > 0) { indentPop(); writeNewline(); } write("],"); writeNewline(); // write members final IDefinitionNode[] members = node.getAllMemberNodes(); write("members: {"); indentPush(); writeNewline(); // constructor write("constructor: " + className); if (members.length > 0) { write(","); writeNewline(); } List<IDefinitionNode> instanceMembers = new ArrayList<IDefinitionNode>(); List<IDefinitionNode> staticMembers = new ArrayList<IDefinitionNode>(); List<IASNode> staticStatements = new ArrayList<IASNode>(); TempTools.fillInstanceMembers(members, instanceMembers); TempTools.fillStaticMembers(members, staticMembers, true, false); TempTools.fillStaticStatements(node, staticStatements, false); len = instanceMembers.size(); i = 0; for (IDefinitionNode mnode : instanceMembers) { if (mnode instanceof IAccessorNode) { if (foundAccessors.containsKey(mnode.getName())) { len--; continue; } getWalker().walk(mnode); } else if (mnode instanceof IFunctionNode) { getWalker().walk(mnode); } else if (mnode instanceof IVariableNode) { getWalker().walk(mnode); } else { write(mnode.getName()); } if (i < len - 1) { write(","); writeNewline(); } i++; } // base class super calls len = baseClassCalls.size(); i = 0; if (len > 0) { write(","); writeNewline(); } for (IDefinition baseCall : baseClassCalls) { write(baseCall.getBaseName() + "$" + inheritenceLevel + ": super$." + baseCall.getBaseName()); if (i < len - 1) { write(","); writeNewline(); } } // end members indentPop(); writeNewline(); write("},"); writeNewline(); len = staticMembers.size(); write("staticMembers: {"); indentPush(); writeNewline(); i = 0; for (IDefinitionNode mnode : staticMembers) { if (mnode instanceof IAccessorNode) { // TODO (mschmalle|AMD) havn't taken care of static accessors if (foundAccessors.containsKey(mnode.getName())) continue; foundAccessors.put(mnode.getName(), mnode); getWalker().walk(mnode); } else if (mnode instanceof IFunctionNode) { getWalker().walk(mnode); } else if (mnode instanceof IVariableNode) { getWalker().walk(mnode); } if (i < len - 1) { write(","); writeNewline(); } i++; } indentPop(); if (len > 0) writeNewline(); write("}"); indentPop(); writeNewline(); write("}));"); // static statements len = staticStatements.size(); if (len > 0) writeNewline(); i = 0; for (IASNode statement : staticStatements) { getWalker().walk(statement); if (!(statement instanceof IBlockNode)) write(";"); if (i < len - 1) writeNewline(); i++; } indentPop(); writeNewline(); write("});"); // end compilation unit } //-------------------------------------------------------------------------- // //-------------------------------------------------------------------------- @Override public void emitField(IVariableNode node) { IVariableDefinition definition = (IVariableDefinition) node .getDefinition(); if (definition.isStatic()) { IClassDefinition parent = (IClassDefinition) definition.getParent(); write(parent.getBaseName()); write("."); write(definition.getBaseName()); write(" = "); emitFieldInitialValue(node); return; } String name = toPrivateName(definition); write(name); write(": "); write("{"); indentPush(); writeNewline(); // field value write("value:"); emitFieldInitialValue(node); write(","); writeNewline(); // writable write("writable:"); write(!(definition instanceof IConstantDefinition) ? "true" : "false"); indentPop(); writeNewline(); write("}"); } private void emitFieldInitialValue(IVariableNode node) { ICompilerProject project = getWalker().getProject(); IVariableDefinition definition = (IVariableDefinition) node .getDefinition(); IExpressionNode valueNode = node.getAssignedValueNode(); if (valueNode != null) getWalker().walk(valueNode); else write(TempTools.toInitialValue(definition, project)); } @Override public void emitGetAccessor(IGetterNode node) { if (foundAccessors.containsKey(node.getName())) return; foundAccessors.put(node.getName(), node); ICompilerProject project = getWalker().getProject(); IAccessorDefinition getter = (IAccessorDefinition) node.getDefinition(); IAccessorDefinition setter = getter .resolveCorrespondingAccessor(project); emitGetterSetterPair(getter, setter); } @Override public void emitSetAccessor(ISetterNode node) { if (foundAccessors.containsKey(node.getName())) return; foundAccessors.put(node.getName(), node); ICompilerProject project = getWalker().getProject(); IAccessorDefinition setter = (IAccessorDefinition) node.getDefinition(); IAccessorDefinition getter = setter .resolveCorrespondingAccessor(project); emitGetterSetterPair(getter, setter); } private void emitGetterSetterPair(IAccessorDefinition getter, IAccessorDefinition setter) { write(getter.getBaseName()); write(": {"); indentPush(); writeNewline(); if (getter != null) { emitAccessor("get", getter); } if (setter != null) { write(","); writeNewline(); emitAccessor("set", setter); } indentPop(); writeNewline(); write("}"); } protected void emitAccessor(String kind, IAccessorDefinition definition) { IFunctionNode fnode = definition.getFunctionNode(); FunctionNode fn = (FunctionNode) fnode; fn.parseFunctionBody(new ArrayList<ICompilerProblem>()); write(kind + ": function "); write(definition.getBaseName() + "$" + kind); emitParameters(fnode.getParametersContainerNode()); emitMethodScope(fnode.getScopedNode()); } @Override public void emitMethod(IFunctionNode node) { if (node.isConstructor()) { emitConstructor(node); return; } FunctionNode fn = (FunctionNode) node; fn.parseFunctionBody(new ArrayList<ICompilerProblem>()); IFunctionDefinition definition = node.getDefinition(); String name = toPrivateName(definition); write(name); write(":"); write(" function "); write(node.getName()); emitParameters(node.getParametersContainerNode()); emitMethodScope(node.getScopedNode()); } @Override public void emitFunctionBlockHeader(IFunctionNode node) { IFunctionDefinition definition = node.getDefinition(); if (node.isConstructor()) { initializingFieldsInConstructor = true; IClassDefinition type = (IClassDefinition) definition .getAncestorOfType(IClassDefinition.class); // emit public fields init values List<IVariableDefinition> fields = TempTools.getFields(type, true); for (IVariableDefinition field : fields) { if (TempTools.isVariableAParameter(field, definition.getParameters())) continue; write("this."); write(field.getBaseName()); write(" = "); emitFieldInitialValue((IVariableNode) field.getNode()); write(";"); writeNewline(); } initializingFieldsInConstructor = false; } emitDefaultParameterCodeBlock(node); } private void emitDefaultParameterCodeBlock(IFunctionNode node) { // TODO (mschmalle|AMD) test for ... rest // if default parameters exist, produce the init code IParameterNode[] pnodes = node.getParameterNodes(); Map<Integer, IParameterNode> defaults = TempTools.getDefaults(pnodes); if (pnodes.length == 0) return; if (defaults != null) { boolean hasBody = node.getScopedNode().getChildCount() > 0; if (!hasBody) { indentPush(); write(ASEmitterTokens.INDENT); } final StringBuilder code = new StringBuilder(); List<IParameterNode> parameters = new ArrayList<IParameterNode>( defaults.values()); Collections.reverse(parameters); int len = defaults.size(); // make the header in reverse order for (IParameterNode pnode : parameters) { if (pnode != null) { code.setLength(0); code.append(ASEmitterTokens.IF.getToken()); code.append(ASEmitterTokens.SPACE.getToken()); code.append(ASEmitterTokens.PAREN_OPEN.getToken()); code.append(JSEmitterTokens.ARGUMENTS.getToken()); code.append(ASEmitterTokens.MEMBER_ACCESS.getToken()); code.append(JSAMDEmitterTokens.LENGTH.getToken()); code.append(ASEmitterTokens.SPACE.getToken()); code.append(ASEmitterTokens.LESS_THAN.getToken()); code.append(ASEmitterTokens.SPACE.getToken()); code.append(len); code.append(ASEmitterTokens.PAREN_CLOSE.getToken()); code.append(ASEmitterTokens.SPACE.getToken()); code.append(ASEmitterTokens.BLOCK_OPEN.getToken()); write(code.toString()); indentPush(); writeNewline(); } len--; } Collections.reverse(parameters); for (int i = 0, n = parameters.size(); i < n; i++) { IParameterNode pnode = parameters.get(i); if (pnode != null) { code.setLength(0); code.append(pnode.getName()); code.append(ASEmitterTokens.SPACE.getToken()); code.append(ASEmitterTokens.EQUAL.getToken()); code.append(ASEmitterTokens.SPACE.getToken()); code.append(pnode.getDefaultValue()); code.append(ASEmitterTokens.SEMICOLON.getToken()); write(code.toString()); indentPop(); writeNewline(); write(ASEmitterTokens.BLOCK_CLOSE); if (i == n - 1 && !hasBody) indentPop(); writeNewline(); } } } } @Override public void emitParameter(IParameterNode node) { getWalker().walk(node.getNameExpressionNode()); } @Override public void emitMemberAccessExpression(IMemberAccessExpressionNode node) { getWalker().walk(node.getLeftOperandNode()); if (!(node.getLeftOperandNode() instanceof ILanguageIdentifierNode)) write(node.getOperator().getOperatorText()); getWalker().walk(node.getRightOperandNode()); } @Override public void emitFunctionCall(IFunctionCallNode node) { if (node.isNewExpression()) { write(ASEmitterTokens.NEW); write(ASEmitterTokens.SPACE); } // IDefinition resolve = node.resolveType(project); // if (NativeUtils.isNative(resolve.getBaseName())) // { // // } getWalker().walk(node.getNameNode()); emitArguments(node.getArgumentsNode()); } @Override public void emitArguments(IContainerNode node) { IContainerNode newNode = node; FunctionCallNode fnode = (FunctionCallNode) node.getParent(); if (TempTools.injectThisArgument(fnode, false)) { IdentifierNode thisNode = new IdentifierNode("this"); newNode = EmitterUtils.insertArgumentsBefore(node, thisNode); } int len = newNode.getChildCount(); write(ASEmitterTokens.PAREN_OPEN); for (int i = 0; i < len; i++) { IExpressionNode inode = (IExpressionNode) newNode.getChild(i); if (inode.getNodeID() == ASTNodeID.IdentifierID) { emitArgumentIdentifier((IIdentifierNode) inode); } else { getWalker().walk(inode); } if (i < len - 1) { writeToken(ASEmitterTokens.COMMA); } } write(ASEmitterTokens.PAREN_CLOSE); } private void emitArgumentIdentifier(IIdentifierNode node) { ITypeDefinition type = node.resolveType(getWalker().getProject()); if (type instanceof ClassTraitsDefinition) { String qualifiedName = type.getQualifiedName(); write(qualifiedName); } else { // XXX A problem? getWalker().walk(node); } } @Override public void emitIdentifier(IIdentifierNode node) { ICompilerProject project = getWalker().getProject(); IDefinition resolve = node.resolve(project); if (TempTools.isBinding(node, project)) { // AS3.bind( this,"secret$1"); // this will happen on the right side of the = sign to bind a methof/function // to a variable write("AS3.bind(this, \"" + toPrivateName(resolve) + "\")"); } else { IExpressionNode leftBase = TempTools.getNode(node, false, project); if (leftBase == node) { if (TempTools.isValidThis(node, project)) write("this."); // in constructor and a type if (initializingFieldsInConstructor && resolve instanceof IClassDefinition) { String name = resolve.getBaseName(); write("(" + name + "._ || " + name + "._$get())"); return; } } if (resolve != null) { // TODO (mschmalle|AMD) optimize String name = toPrivateName(resolve); if (NativeUtils.isNative(name)) exportWriter.addDependency(name, name, true, false); if (node.getParent() instanceof IMemberAccessExpressionNode) { IMemberAccessExpressionNode mnode = (IMemberAccessExpressionNode) node .getParent(); if (mnode.getLeftOperandNode().getNodeID() == ASTNodeID.SuperID) { IIdentifierNode lnode = (IIdentifierNode) mnode .getRightOperandNode(); IClassNode cnode = (IClassNode) node .getAncestorOfType(IClassNode.class); initializeInheritenceLevel(cnode.getDefinition()); // super.foo(); write("this."); write(lnode.getName() + "$" + inheritenceLevel); baseClassCalls.add(resolve); return; } } write(name); } else { // no definition, just plain ole identifer write(node.getName()); } } } @Override protected void emitType(IExpressionNode node) { } @Override public void emitLanguageIdentifier(ILanguageIdentifierNode node) { if (node.getKind() == ILanguageIdentifierNode.LanguageIdentifierKind.ANY_TYPE) { write(""); } else if (node.getKind() == ILanguageIdentifierNode.LanguageIdentifierKind.REST) { write(""); } else if (node.getKind() == ILanguageIdentifierNode.LanguageIdentifierKind.SUPER) { IIdentifierNode inode = (IIdentifierNode) node; if (inode.getParent() instanceof IMemberAccessExpressionNode) { } else { write("Super.call"); } } else if (node.getKind() == ILanguageIdentifierNode.LanguageIdentifierKind.THIS) { write(""); } else if (node.getKind() == ILanguageIdentifierNode.LanguageIdentifierKind.VOID) { write(""); } } private String toPrivateName(IDefinition definition) { if (definition instanceof ITypeDefinition) return definition.getBaseName(); if (!definition.isPrivate()) return definition.getBaseName(); initializeInheritenceLevel(definition); return definition.getBaseName() + "$" + inheritenceLevel; } void initializeInheritenceLevel(IDefinition definition) { if (inheritenceLevel != -1) return; IClassDefinition cdefinition = null; if (definition instanceof IClassDefinition) cdefinition = (IClassDefinition) definition; else cdefinition = (IClassDefinition) definition .getAncestorOfType(IClassDefinition.class); ICompilerProject project = getWalker().getProject(); IClassDefinition[] ancestry = cdefinition.resolveAncestry(project); inheritenceLevel = ancestry.length - 1; } }