blob: e9413b4cbdd0811fd11279c213780455111e068a [file] [log] [blame]
/*
*
* 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.royale.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.royale.compiler.codegen.js.amd.IJSAMDDocEmitter;
import org.apache.royale.compiler.codegen.js.amd.IJSAMDEmitter;
import org.apache.royale.compiler.definitions.IAccessorDefinition;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IConstantDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.IPackageDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.JSEmitter;
import org.apache.royale.compiler.internal.codegen.js.JSEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.utils.EmitterUtils;
import org.apache.royale.compiler.internal.definitions.ClassTraitsDefinition;
import org.apache.royale.compiler.internal.tree.as.FunctionCallNode;
import org.apache.royale.compiler.internal.tree.as.FunctionNode;
import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IAccessorNode;
import org.apache.royale.compiler.tree.as.IBlockNode;
import org.apache.royale.compiler.tree.as.IClassNode;
import org.apache.royale.compiler.tree.as.IContainerNode;
import org.apache.royale.compiler.tree.as.IDefinitionNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IFunctionCallNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IGetterNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IInterfaceNode;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.tree.as.ISetterNode;
import org.apache.royale.compiler.tree.as.ITypeNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
import org.apache.royale.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;
}
}