blob: eeec0c25f925d36464d23a073f242cd028e54378 [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.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition.FunctionClassification;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition;
import org.apache.royale.compiler.internal.codegen.js.JSSessionModel;
import org.apache.royale.compiler.internal.definitions.AccessorDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.InterfaceDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition.INamepaceDeclarationDirective;
import org.apache.royale.compiler.internal.definitions.ParameterDefinition;
import org.apache.royale.compiler.internal.definitions.VariableDefinition;
import org.apache.royale.compiler.internal.tree.as.ContainerNode;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.ParameterNode;
import org.apache.royale.compiler.projects.ICompilerProject;
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.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.IFunctionNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.INamespaceNode;
import org.apache.royale.compiler.tree.as.IPackageNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.tree.as.ITypeNode;
import org.apache.royale.compiler.tree.as.IUnaryOperatorNode;
import org.apache.royale.compiler.tree.as.IVariableExpressionNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
import org.apache.royale.compiler.utils.NativeUtils;
/**
* Various static methods used in shared emitter logic.
*/
public class EmitterUtils
{
public static ITypeNode findTypeNode(IPackageNode node)
{
IScopedNode scope = node.getScopedNode();
for (int i = 0; i < scope.getChildCount(); i++)
{
IASNode child = scope.getChild(i);
if (child instanceof ITypeNode)
return (ITypeNode) child;
}
return null;
}
public static ITypeDefinition findType(Collection<IDefinition> definitions)
{
for (IDefinition definition : definitions)
{
if (definition instanceof ITypeDefinition)
return (ITypeDefinition) definition;
}
return null;
}
public static INamepaceDeclarationDirective findNamespace(Collection<IDefinition> definitions)
{
for (IDefinition definition : definitions)
{
if (definition instanceof INamepaceDeclarationDirective)
return (INamepaceDeclarationDirective) definition;
}
return null;
}
public static INamespaceNode findNamespaceNode(IPackageNode node)
{
IScopedNode scope = node.getScopedNode();
for (int i = 0; i < scope.getChildCount(); i++)
{
IASNode child = scope.getChild(i);
if (child instanceof INamespaceNode)
return (INamespaceNode) child;
}
return null;
}
public static IFunctionDefinition findFunction(Collection<IDefinition> definitions)
{
for (IDefinition definition : definitions)
{
if (definition instanceof IFunctionDefinition)
return (IFunctionDefinition) definition;
}
return null;
}
public static IFunctionNode findFunctionNode(IPackageNode node)
{
IScopedNode scope = node.getScopedNode();
for (int i = 0; i < scope.getChildCount(); i++)
{
IASNode child = scope.getChild(i);
if (child instanceof IFunctionNode)
return (IFunctionNode) child;
}
return null;
}
public static IVariableNode findVariableNode(IPackageNode node)
{
IScopedNode scope = node.getScopedNode();
for (int i = 0; i < scope.getChildCount(); i++)
{
IASNode child = scope.getChild(i);
if (child instanceof IVariableNode)
return (IVariableNode) child;
}
return null;
}
public static IVariableDefinition findVariable(Collection<IDefinition> definitions)
{
for (IDefinition definition : definitions)
{
if (definition instanceof IVariableDefinition)
return (IVariableDefinition) definition;
}
return null;
}
public static ITypeDefinition getTypeDefinition(IDefinitionNode node)
{
ITypeNode tnode = (ITypeNode) node.getAncestorOfType(ITypeNode.class);
if (tnode != null)
{
return (ITypeDefinition) tnode.getDefinition();
}
return null;
}
public static boolean isSameClass(IDefinition pdef, IDefinition thisClass,
ICompilerProject project)
{
if (pdef == thisClass)
return true;
IDefinition cdef = ((ClassDefinition) thisClass)
.resolveBaseClass(project);
while (cdef != null)
{
// needs to be a loop
if (cdef == pdef)
return true;
cdef = ((ClassDefinition) cdef).resolveBaseClass(project);
}
return false;
}
public static boolean hasSuperClass(ICompilerProject project,
IDefinitionNode node)
{
IClassDefinition superClassDefinition = getSuperClassDefinition(node,
project);
// XXX (mschmalle) this is nulling for MXML super class, figure out why
if (superClassDefinition == null)
return false;
String qname = superClassDefinition.getQualifiedName();
return superClassDefinition != null
&& !qname.equals(IASLanguageConstants.Object);
}
public static boolean hasSuperCall(IScopedNode node)
{
for (int i = node.getChildCount() - 1; i > -1; i--)
{
IASNode cnode = node.getChild(i);
if (cnode.getNodeID() == ASTNodeID.FunctionCallID
&& cnode.getChild(0).getNodeID() == ASTNodeID.SuperID)
return true;
}
return false;
}
public static boolean hasBody(IFunctionNode node)
{
IScopedNode scope = node.getScopedNode();
return scope.getChildCount() > 0;
}
public static IClassDefinition getSuperClassDefinition(
IDefinitionNode node, ICompilerProject project)
{
IDefinition parent = node.getDefinition().getParent();
if (parent instanceof IClassDefinition)
{
IClassDefinition parentClassDef = (IClassDefinition) parent;
IClassDefinition superClass = parentClassDef.resolveBaseClass(project);
return superClass;
}
return null;
}
public static List<String> resolveImports(ITypeDefinition type)
{
ArrayList<String> list = new ArrayList<String>();
IScopedNode scopeNode = type.getContainedScope().getScopeNode();
if (scopeNode != null)
{
scopeNode.getAllImports(list);
}
else
{
// MXML
ClassDefinition cdefinition = (ClassDefinition) type;
String[] implicitImports = cdefinition.getImplicitImports();
for (String imp : implicitImports)
{
list.add(imp);
}
}
return list;
}
public static IClassDefinition getClassDefinition(IDefinitionNode node)
{
IClassNode tnode = (IClassNode) node
.getAncestorOfType(IClassNode.class);
return (tnode != null) ? tnode.getDefinition() : null;
}
public static IParameterNode getRest(IParameterNode[] nodes)
{
for (IParameterNode node : nodes)
{
if (node.isRest())
return node;
}
return null;
}
public static Map<Integer, IParameterNode> getDefaults(
IParameterNode[] nodes)
{
Map<Integer, IParameterNode> result = new HashMap<Integer, IParameterNode>();
int i = 0;
boolean hasDefaults = false;
for (IParameterNode node : nodes)
{
if (node.hasDefaultValue())
{
hasDefaults = true;
result.put(i, node);
}
else
{
result.put(i, null);
}
i++;
}
if (!hasDefaults)
return null;
return result;
}
public static boolean writeThis(ICompilerProject project,
JSSessionModel model, IIdentifierNode node)
{
IClassNode classNode = (IClassNode) node
.getAncestorOfType(IClassNode.class);
IDefinition nodeDef = node.resolve(project);
IASNode parentNode = node.getParent();
ASTNodeID parentNodeId = parentNode.getNodeID();
IASNode firstChild = parentNode.getChild(0);
final IClassDefinition thisClass = model.getCurrentClass();
boolean identifierIsMemberAccess = parentNodeId == ASTNodeID.MemberAccessExpressionID;
if (nodeDef instanceof ParameterDefinition)
return false;
if (nodeDef instanceof InterfaceDefinition)
return false;
if (nodeDef instanceof ClassDefinition)
return false;
if (classNode == null) // script in MXML and AS interface definitions
{
if (parentNodeId == ASTNodeID.FunctionCallID && model.inE4xFilter)
{
// instance methods must be qualified with 'this'?
// or maybe we need to test if identifier exists on XML/XMLList
return false;
}
if (nodeDef instanceof VariableDefinition)
{
IDefinition pdef = ((VariableDefinition) nodeDef).getParent();
if (thisClass == null || !isSameClass(pdef, thisClass, project))
return false;
if (identifierIsMemberAccess)
return node == firstChild;
return parentNodeId == ASTNodeID.ContainerID
|| !(parentNode instanceof ParameterNode);
}
else if (nodeDef instanceof AccessorDefinition)
{
IDefinition pdef = ((AccessorDefinition) nodeDef).getParent();
if (thisClass == null || !isSameClass(pdef, thisClass, project))
return false;
if (identifierIsMemberAccess)
return node == firstChild;
return true;
}
else if (parentNodeId == ASTNodeID.ContainerID
&& nodeDef instanceof FunctionDefinition)
{
return ((FunctionDefinition) nodeDef)
.getFunctionClassification() == FunctionClassification.CLASS_MEMBER; // for 'goog.bind'
}
else
{
boolean isFileOrPackageMember = false;
boolean isLocalFunction = false;
if(nodeDef instanceof FunctionDefinition)
{
FunctionClassification classification = ((FunctionDefinition) nodeDef).getFunctionClassification();
if(classification == FunctionClassification.FILE_MEMBER ||
classification == FunctionClassification.PACKAGE_MEMBER)
{
isFileOrPackageMember = true;
}
else if (!identifierIsMemberAccess && classification == FunctionClassification.CLASS_MEMBER &&
isClassMember(project, nodeDef, thisClass))
{
return true;
}
else if (classification == FunctionClassification.LOCAL)
{
isLocalFunction = true;
}
}
return parentNodeId == ASTNodeID.FunctionCallID
&& !(nodeDef instanceof AccessorDefinition)
&& !identifierIsMemberAccess
&& !isFileOrPackageMember
&& !isLocalFunction;
}
}
else
{
if (parentNodeId == ASTNodeID.FunctionCallID && model.inE4xFilter)
{
// instance methods must be qualified with 'this'?
// or maybe we need to test if identifier exists on XML/XMLList
return false;
}
if (nodeDef != null
&& isClassMember(project, nodeDef, classNode))
{
if (identifierIsMemberAccess)
{
return node == firstChild;
}
else
{
boolean identifierIsLocalFunction = nodeDef instanceof FunctionDefinition
&& !(nodeDef instanceof AccessorDefinition)
&& ((FunctionDefinition) nodeDef)
.getFunctionClassification() == IFunctionDefinition.FunctionClassification.LOCAL;
return !identifierIsLocalFunction;
}
}
}
return false;
}
public static boolean isClassMember(ICompilerProject project,
IDefinition nodeDef, IClassNode classNode)
{
IDefinition parentDef = nodeDef.getParent();
if (nodeDef.isInternal() && (!(parentDef instanceof ClassDefinition)))
return false;
IClassDefinition cdef = classNode.getDefinition();
return parentDef == cdef || (parentDef instanceof ClassDefinition && cdef.isInstanceOf((ClassDefinition)parentDef, project));
}
public static boolean isClassMember(ICompilerProject project,
IDefinition nodeDef, IClassDefinition classDef)
{
IDefinition parentDef = nodeDef.getParent();
if (nodeDef.isInternal() && (!(parentDef instanceof ClassDefinition)))
return false;
return parentDef == classDef || (parentDef instanceof ClassDefinition && ((ClassDefinition)parentDef).isInstanceOf(classDef, project));
}
public static boolean writeE4xFilterNode(ICompilerProject project,
JSSessionModel model, IExpressionNode node)
{
if (!model.inE4xFilter) return false;
IDefinition nodeDef = node.resolve(project);
IASNode parentNode = node.getParent();
// ASTNodeID parentNodeId = parentNode.getNodeID();
IASNode firstChild = parentNode.getChild(0);
// final IClassDefinition thisClass = model.getCurrentClass();
// boolean identifierIsMemberAccess = parentNodeId == ASTNodeID.MemberAccessExpressionID;
if (parentNode instanceof IUnaryOperatorNode)
return false;
if (nodeDef instanceof ParameterDefinition)
return false;
if (nodeDef instanceof InterfaceDefinition)
return false;
if (nodeDef instanceof ClassDefinition)
return false;
if (nodeDef instanceof VariableDefinition)
{
List<IVariableNode> list = model.getVars();
for (IVariableNode element : list) {
if(element.getQualifiedName().equals(((IIdentifierNode)node).getName()))
return false;
}
}
if (node == firstChild)
return true;
return false;
}
public static boolean isScalar(IExpressionNode node)
{
ASTNodeID id = node.getNodeID();
if (id == ASTNodeID.LiteralBooleanID ||
id == ASTNodeID.LiteralIntegerID ||
id == ASTNodeID.LiteralIntegerZeroID ||
id == ASTNodeID.LiteralDoubleID ||
id == ASTNodeID.LiteralNullID ||
id == ASTNodeID.LiteralNumberID ||
id == ASTNodeID.LiteralRegexID ||
id == ASTNodeID.LiteralStringID ||
id == ASTNodeID.LiteralUintID)
return true;
if (id == ASTNodeID.IdentifierID)
{
IIdentifierNode idnode = (IIdentifierNode)node;
String idname = idnode.getName();
if (idname.equals(NativeUtils.NativeASType.Infinity.name()) ||
idname.equals(NativeUtils.NativeASType.undefined.name()) ||
idname.equals(NativeUtils.NativeASType.NaN.name()))
return true;
}
// special case -Infinity
if (id == ASTNodeID.Op_SubtractID &&
node.getChildCount() == 1)
{
IASNode child = node.getChild(0);
if (child.getNodeID() == ASTNodeID.IdentifierID)
{
IIdentifierNode idnode = (IIdentifierNode)child;
String idname = idnode.getName();
if (idname.equals(NativeUtils.NativeASType.Infinity.name()))
return true;
}
}
return false;
}
// return true if the node is an expression that may not work
// as the initial value of a static var at
// static initialization time. Such as a function call to
// another static method in the class.
// Non-static initializers have different rules: even simple object
// and arrays need to be created for each instance, but for statics
// simple objects and arras are ok.
public static boolean needsStaticInitializer(String node, String className)
{
return node.contains(className);
}
public static IContainerNode insertArgumentsBefore(IContainerNode argumentsNode, IASNode... nodes)
{
int originalLength = argumentsNode.getChildCount();
int extraLength = nodes.length;
ContainerNode result = new ContainerNode(originalLength + extraLength);
result.setSourcePath(argumentsNode.getSourcePath());
result.span(argumentsNode);
result.setParent((NodeBase) argumentsNode.getParent());
for (int i = 0; i < extraLength; i++)
{
NodeBase node = (NodeBase) nodes[i];
node.setSourcePath(argumentsNode.getSourcePath());
result.addItem(node);
}
for (int i = 0; i < originalLength; i++)
{
result.addItem((NodeBase) argumentsNode.getChild(i));
}
return result;
}
public static IContainerNode insertArgumentsAfter(IContainerNode argumentsNode, IASNode... nodes)
{
int originalLength = argumentsNode.getChildCount();
int extraLength = nodes.length;
ContainerNode result = new ContainerNode(originalLength + extraLength);
result.setSourcePath(argumentsNode.getSourcePath());
result.span(argumentsNode);
result.setParent((NodeBase) argumentsNode.getParent());
for (int i = 0; i < originalLength; i++)
{
result.addItem((NodeBase) argumentsNode.getChild(i));
}
for (int i = 0; i < extraLength; i++)
{
NodeBase node = (NodeBase) nodes[i];
node.setSourcePath(argumentsNode.getSourcePath());
result.addItem(node);
}
return result;
}
public static IContainerNode insertArgumentsAt(IContainerNode argumentsNode, int index, IASNode... nodes)
{
int originalLength = argumentsNode.getChildCount();
int extraLength = nodes.length;
ContainerNode result = new ContainerNode(originalLength + extraLength);
result.setSourcePath(argumentsNode.getSourcePath());
result.span(argumentsNode);
result.setParent((NodeBase) argumentsNode.getParent());
for (int i = 0; i < originalLength; i++)
{
if(i < index)
{
result.addItem((NodeBase) argumentsNode.getChild(i));
}
else
{
if (i == index)
{
for (IASNode node : nodes)
{
NodeBase n = (NodeBase) node;
n.setSourcePath(argumentsNode.getSourcePath());
result.addItem(n);
}
}
result.addItem((NodeBase) argumentsNode.getChild(i));
}
}
return result;
}
public static boolean isImplicit(IContainerNode node)
{
return node.getContainerType() == IContainerNode.ContainerType.IMPLICIT
|| node.getContainerType() == IContainerNode.ContainerType.SYNTHESIZED;
}
public static boolean needsDefaultValue(IVariableNode node, boolean defaultInitializers, ICompilerProject project)
{
if (node == null)
{
return false;
}
if (node instanceof IParameterNode)
{
return false;
}
if (node instanceof IAccessorNode)
{
return false;
}
IExpressionNode assignedValueNode = node.getAssignedValueNode();
if (assignedValueNode != null)
{
//already has an assigned value, so it doesn't need to be
//hoisted
return false;
}
IASNode parentNode = node.getParent();
if (parentNode instanceof IVariableExpressionNode)
{
//ignore for-in loops
return false;
}
IExpressionNode variableTypeNode = node.getVariableTypeNode();
if (variableTypeNode == null)
{
return false;
}
IDefinition varTypeDef = variableTypeNode.resolve(project);
if (varTypeDef == null)
{
return false;
}
if (IASLanguageConstants.ANY_TYPE.equals(varTypeDef.getQualifiedName()))
{
return false;
}
if (project.getBuiltinType(BuiltinType.ANY_TYPE).equals(varTypeDef)
|| project.getBuiltinType(BuiltinType.ANY_TYPE).equals(varTypeDef)
|| project.getBuiltinType(BuiltinType.ANY_TYPE).equals(varTypeDef))
{
return false;
}
if (project.getBuiltinType(BuiltinType.INT).equals(varTypeDef)
|| project.getBuiltinType(BuiltinType.UINT).equals(varTypeDef))
{
//always true, regardless of -js-default-initializers
return true;
}
return defaultInitializers;
}
}