![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.royale.utils.ArrayLikeUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
The Apache Royale Compiler
/*
*
* 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.utils;
import org.apache.royale.compiler.common.SourceLocation;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.definitions.*;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.parsing.as.ASToken;
import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.*;
import org.apache.royale.compiler.problems.ArrayLikeConfigurationErrorProblem;
import org.apache.royale.compiler.problems.ArrayLikeUsageErrorProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.projects.IRoyaleProject;
import org.apache.royale.compiler.tree.as.*;
import java.util.*;
/**
* Support for processing a variety of characteristics via metadata on classes or interfaces
* that translate Array like access and iteration from as3 language level to implementation support for
* the annotated definition(s)
* example:
* [RoyaleArrayLike(getValue="getItemAt",setValue="setItemAt",length="length",lengthAccess="getter")]
*
* This class contains utilities to support both mutating AST of 'for each' loops with target (rightOperand) types that
* are classes or interfaces which have [RoyaleArrayLike] metadata. It also supports mutation of array-like
* dynamic access or assignment when the dynamic access keys are numeric types (int, uint, Number).
* Subclasses of RoyaleArrayLike classes or implementers of interfaces which are RoyaleArrayLike
* 'inherit' the RoyaleArrayLike special treatment by the compiler.
*/
public class ArrayLikeUtil
{
//the metadata arg that specifies the member name for accessing length value of and instance of the decorated type
final private static String LENGTH_ARG = "length";
//the metadata arg that specifies the type of access that the length arg specifies ('getter' or 'method', default value is 'getter', see defaults below)
final private static String LENGTH_ACCESS_ARG = "lengthAccess";
//the metadata arg that specifies how numeric index level 'get' is performed
final private static String GETVALUE_ARG = "getValue";
//the metadata arg that specifies how numeric index level 'set' is performed
final private static String SETVALUE_ARG = "setValue";
//the metadata arg that specifies the order of arguments used in the setter ('value,index' or 'index,value', default value is 'value,index')
final private static String SETTER_ARG_SEQUENCE_ARG = "setterArgSequence";
//the valid SETTER_ARG_SEQUENCE_ARG value options.
//Whether the 'setter' has the index arg first or the value to be set at the specified index (or vice versa)
//checking the method signature in the class to determine this is not appropriate, because in the general case
//there may be classes whose values are also typed as numeric, so it should be specified. (default is value,index, see defaults below)
final private static String SETTER_ARG_SEQUENCE_VALUE_INDEX = "value,index";
final private static String SETTER_ARG_SEQUENCE_INDEX_VALUE = "index,value";
//the valid options for length access. Used when generated iteration support in for-each loops
final private static String LENGTH_ACCESS_METHOD = "method";
final private static String LENGTH_ACCESS_GETTER = "getter";
//a value for either getValue or setValue that indicates regular array access should be used,
//This results in no alteration compared to original source code for either the 'getter' or 'setter'
// (or both, if both 'getValue' and 'setValue' have this value)
final private static String INDEX_ACCESS_UNCHANGED = "[]";
//default values for certain args that can be omitted.
final private static String LENGTH_ACCESS_DEFAULT = LENGTH_ACCESS_GETTER;
final private static String SETTER_ARG_SEQUENCE_DEFAULT = SETTER_ARG_SEQUENCE_VALUE_INDEX;
//this is currently 'assumed safe', based on the chance of naming collision being extremely unlikely
//with an actual user-coded variable name. It also assumes that the target platform will optimize its own local variable names,
//therefore there is no attempt to make a 'short' name.
final private static String ARRAY_LIKE_FOREACH_ITERATOR_VARNAME_BASE = "royale$for$Each$Iterator";
/**
* todo Consider the possibility of specific iterator factories instead of one generic function for all... the qName for the specific function could be specified in the Metadata def.
* These could simply take the instance as an argument and already know the way to construct the 'iterator' because they are specific
* (e.g. collections could have a specific iterator function, XMLish its own, BinaryData its own etc).
*/
private final static String ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC_QNAME = "org.apache.royale.language.iterator.arrayLike";
private final static String ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC = "arrayLike";
final static String ARRAYLIKE_HAS_NEXT = "hasNext";
final static String ARRAYLIKE_GET_NEXT = "next";
private static String wrapQuotes(String original){
return "'" + original + "'";
}
/**
* pre-processes dynamic access nodes, replacing them where applicable with get or set value calls
* on the arrayLike instance
*/
public static void preProcessGetterSetters(ICompilerProject project, ContainerNode node, NodeBase lower ) {
if (!(project instanceof IRoyaleProject)) {
return;
}
NodeBase parent = lower != null ? lower : node;
int childCount = parent.getChildCount();
for (int i=0; i < childCount; i++) {
IASNode child = parent.getChild(i);
//future: consider ways to consolidate/abstract these various checks
if (child instanceof BinaryOperatorAssignmentNode) {
if (((BinaryOperatorAssignmentNode) child).getLeftOperandNode() instanceof DynamicAccessNode) {
DynamicAccessNode dynNode = (DynamicAccessNode) ((BinaryOperatorAssignmentNode) child).getLeftOperandNode();
if (isArrayLikeCandidate(dynNode, project)) {
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(target, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
String setterArg;
setterArg = getSetterArg(arrayLikeTag);
if (!setterArg.equals(INDEX_ACCESS_UNCHANGED)) { //otherwise we leave it as is
//get the FunctionCallNode replacement's arguments from the original assignment
ExpressionNodeBase indexArg = (ExpressionNodeBase) dynNode.getRightOperandNode();
ExpressionNodeBase valueArg = (ExpressionNodeBase) ((BinaryOperatorAssignmentNode) child).getRightOperandNode();
FunctionCallNode replacement = createDynamicAccessMutation(dynNode, setterArg);
String argSequence = getSetterArgSequenceArg(arrayLikeTag);
if (argSequence.equals(SETTER_ARG_SEQUENCE_VALUE_INDEX)) {
replacement.getArgumentsNode().addChild(valueArg);
replacement.getArgumentsNode().addChild(indexArg);
} else { //index,value instead
replacement.getArgumentsNode().addChild(indexArg);
replacement.getArgumentsNode().addChild(valueArg);
}
replacement.getArgumentsNode().setParent(replacement);
if (parent instanceof ContainerNode) {
((ContainerNode) parent).removeItem((NodeBase) child);
((ContainerNode) parent).addChild(replacement, i);
((BinaryOperatorAssignmentNode) child).setParent(null);
child = replacement; //we need to allow for recursive checking of the assigned value as well as some other 'getter'
} /*else {
System.out.println("Problem: " + (node.getNodeID() + ":" + node.getLine()));
}*/
}
}
}
} else if (child instanceof DynamicAccessNode) {
DynamicAccessNode dynNode = (DynamicAccessNode)child;
if (isArrayLikeCandidate(dynNode, project)) {
if (parent instanceof BaseStatementExpressionNode) {
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(target, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
String getterArg = getGetterArg(arrayLikeTag);
if (!getterArg.equals(INDEX_ACCESS_UNCHANGED)) {
ExpressionNodeBase indexArg = (ExpressionNodeBase) dynNode.getRightOperandNode();
FunctionCallNode replacement = createDynamicAccessMutation(dynNode, getterArg);
replacement.getArgumentsNode().addChild(indexArg);
replacement.getArgumentsNode().setParent(replacement);
((BaseStatementExpressionNode) parent).setStatementExpression(replacement);
replacement.setParent( parent);
dynNode.setParent(null);
child = replacement;
}
}
else if (parent instanceof ContainerNode) {
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(target, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
String getterArg = getGetterArg(arrayLikeTag);
if (!getterArg.equals(INDEX_ACCESS_UNCHANGED)) { //otherwise we leave it as is
ExpressionNodeBase indexArg = (ExpressionNodeBase) dynNode.getRightOperandNode();
FunctionCallNode replacement = createDynamicAccessMutation(dynNode, getterArg);
replacement.getArgumentsNode().addChild(indexArg);
replacement.getArgumentsNode().setParent(replacement);
((ContainerNode) parent).removeItem((NodeBase) child);
((ContainerNode) parent).addChild(replacement, i);
dynNode.setParent(null);
child = replacement;
}
} else if (parent instanceof BinaryOperatorNodeBase) {
BinaryOperatorNodeBase binaryOp = (BinaryOperatorNodeBase) parent;
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(target, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
String getterArg = getGetterArg(arrayLikeTag);
if (!getterArg.equals(INDEX_ACCESS_UNCHANGED)) { //otherwise we leave it as is
ExpressionNodeBase indexArg = (ExpressionNodeBase) dynNode.getRightOperandNode();
FunctionCallNode replacement = createDynamicAccessMutation(dynNode, getterArg);
replacement.getArgumentsNode().addChild(indexArg);
replacement.getArgumentsNode().setParent(replacement);
if (binaryOp.getLeftOperandNode() == child) {
binaryOp.setLeftOperandNode(replacement);
} else {
binaryOp.setRightOperandNode(replacement);
}
replacement.setParent(binaryOp);
dynNode.setParent(null);
child = replacement;
}
} else if (parent instanceof VariableNode) {
VariableNode variableNode = (VariableNode) parent;
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(target, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
String getterArg = getGetterArg(arrayLikeTag);
if (!getterArg.equals(INDEX_ACCESS_UNCHANGED)) { //otherwise we leave it as is
ExpressionNodeBase indexArg = (ExpressionNodeBase) dynNode.getRightOperandNode();
FunctionCallNode replacement = createDynamicAccessMutation(dynNode, getterArg);
replacement.getArgumentsNode().addChild(indexArg);
replacement.getArgumentsNode().setParent(replacement);
variableNode.setAssignedValue(null, replacement);
replacement.setParent(variableNode);
dynNode.setParent(null);
child = replacement;
}
} else if (parent instanceof UnaryOperatorNodeBase) {
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(target, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
String getterArg = getGetterArg(arrayLikeTag);
String setterArg = getSetterArg(arrayLikeTag);
if (!(INDEX_ACCESS_UNCHANGED.equals(getterArg) && INDEX_ACCESS_UNCHANGED.equals(setterArg))) {
//report an error - unless we have native get/set bracket [] access (where we can assume it should work), we can't easily migrate unary operators
UnaryOperatorNodeBase unaryOperatorNodeBase = (UnaryOperatorNodeBase) parent;
ArrayLikeUsageErrorProblem usageError;
SourceLocation loc = new SourceLocation();
loc.setSourcePath(unaryOperatorNodeBase.getSourcePath());
if (unaryOperatorNodeBase.getOperandNode().getAbsoluteStart() > unaryOperatorNodeBase.getOperatorAbsoluteStart()) {
//prepended unary operator
loc.setColumn(unaryOperatorNodeBase.getColumn());
loc.setLine(unaryOperatorNodeBase.getLine());
loc.setEndColumn(unaryOperatorNodeBase.getOperatorAbsoluteStart() - unaryOperatorNodeBase.getOperandNode().getAbsoluteStart());
loc.setEndLine(unaryOperatorNodeBase.getLine());
loc.setStart(unaryOperatorNodeBase.getStart());
loc.setEnd(unaryOperatorNodeBase.getOperatorAbsoluteStart() - 1);
} else {
//post-pended unary operator
loc.setColumn(unaryOperatorNodeBase.getOperandNode().getEndColumn());
loc.setLine(unaryOperatorNodeBase.getOperandNode().getEndLine());
loc.setEndColumn(unaryOperatorNodeBase.getEndColumn());
loc.setEndLine(unaryOperatorNodeBase.getEndLine());
loc.setStart(unaryOperatorNodeBase.getOperandNode().getAbsoluteStart());
loc.setStart(unaryOperatorNodeBase.getAbsoluteEnd());
}
usageError = new ArrayLikeUsageErrorProblem(loc,
"Unary Operation not supported");
project.getProblems().add(usageError);
}
} /*else {
System.out.println("Skipped: " + (child.getNodeID()));
}*/
}
}/* else {
System.out.println("Skipped: " + (child.getNodeID()));
}*/
if (child instanceof FixedChildrenNode || child instanceof TreeNode) {
preProcessGetterSetters(/*searchScope,*/ project, node, (NodeBase) child);
}
}
}
private static boolean isArrayLikeCandidate(DynamicAccessNode dynNode, ICompilerProject project) {
if (!(project instanceof IRoyaleProject)) {
return false;
}
boolean isCandidate = false;
IDefinition dynType = null;
IExpressionNode rightNode = dynNode.getRightOperandNode();
if (rightNode != null)
{
dynType = rightNode.resolveType(project);
}
//first check to see if the access is numeric... if it is not then we consider that it is not a candidate
if (project.getBuiltinType(IASLanguageConstants.BuiltinType.NUMBER).equals(dynType)
|| project.getBuiltinType(IASLanguageConstants.BuiltinType.UINT).equals(dynType)
|| project.getBuiltinType(IASLanguageConstants.BuiltinType.INT).equals(dynType)
) {
IDefinition target = dynNode.getLeftOperandNode().resolveType(project);
isCandidate = ArrayLikeUtil.isArrayLike(target, project);
}
return isCandidate;
}
private static FunctionCallNode createDynamicAccessMutation(DynamicAccessNode original, String methodName){
ExpressionNodeBase base = (ExpressionNodeBase) original.getLeftOperandNode();
IdentifierNode methodCallName = new IdentifierNode(methodName);
MemberAccessExpressionNode nameNode;
nameNode = new MemberAccessExpressionNode(base, null, methodCallName);
base.setParent(nameNode);
methodCallName.setParent(nameNode);
FunctionCallNode replacement = new FunctionCallNode(nameNode);
nameNode.setParent(replacement);
//the scaffolding for the replacement is set up... adding the arguments will be managed by the call site
return replacement;
}
/**
* main support for loop mutations to support RoyaleArrayLikes as targets of 'for each' loops
* @param searchScope
* @param project
*/
public static void preProcessLoopChecks(ASScope searchScope, IRoyaleProject project) {
ScopedBlockNode funcScopeNode = (ScopedBlockNode) searchScope.getScopeNode();
IForLoopNode[] forLoops = searchScope.getLoopChecks(true);
List forLoopList = Arrays.asList(forLoops);
boolean importAdded = false;
ArrayList usedIterators = new ArrayList();
for (IForLoopNode loopNode : forLoopList) {
int depth = 0;
IASNode nodeCheck = loopNode;
while (nodeCheck.getParent() != null && nodeCheck.getParent() != funcScopeNode) {
if (nodeCheck.getParent() instanceof IForLoopNode && forLoopList.indexOf(nodeCheck.getParent()) != -1) {
depth++;
}
nodeCheck = nodeCheck.getParent();
}
//are we dealing with a regular for..in or a for each..in loop:
boolean isForeach = loopNode.getKind() == IForLoopNode.ForLoopKind.FOR_EACH;
//create a valid name for the iterator at the current depth of for-each loops, re-using previous declared variables, where possible
String arrIter = ARRAY_LIKE_FOREACH_ITERATOR_VARNAME_BASE + depth;
IDefinition targetType;
final IDefinition xmlListDef = project.getBuiltinType(IASLanguageConstants.BuiltinType.XMLLIST);
try {
//check the rightOperandType
BinaryOperatorInNode conditionalExpressions = (BinaryOperatorInNode) loopNode.getConditionalExpressionNodes()[0];
if (conditionalExpressions.getRightOperandNode() instanceof IFunctionCallNode) {
IASNode nameNode = ((FunctionCallNode) conditionalExpressions.getRightOperandNode()).getNameNode();
if (nameNode instanceof IMemberAccessExpressionNode) {
IFunctionDefinition funcDef = (IFunctionDefinition) ((IMemberAccessExpressionNode) nameNode).getRightOperandNode().resolve(project);
targetType = funcDef.resolveReturnType(project);
} else {
//back to error until we cover all cases
targetType = conditionalExpressions.getRightOperandNode().resolveType(project);
}
} else {
if (conditionalExpressions.getRightOperandNode() instanceof IMemberAccessExpressionNode) {
//this does not resolve... check for XMLish on the left
targetType = conditionalExpressions.getRightOperandNode().resolveType(project);
IExpressionNode left = ((IMemberAccessExpressionNode) conditionalExpressions.getRightOperandNode()).getLeftOperandNode();
while (targetType == null && left != null) {
targetType = left.resolveType(project);
if (targetType == null) {
if (left instanceof IMemberAccessExpressionNode || left instanceof IDynamicAccessNode) {
left = ((IBinaryOperatorNode) left).getLeftOperandNode();
} else {
left = null;
}
} else {
if (SemanticUtils.isXMLish(targetType, project)) {
//assume we have an XMLList
targetType = xmlListDef;
}
if (targetType != xmlListDef) {
//this does not make sense, so ignore it or add a problem?
targetType = null;
left = null;
} /*else {
System.out.println("Searching, found left target type to "+targetType.getQualifiedName());
}*/
}
}
} else {
targetType = conditionalExpressions.getRightOperandNode().resolveType(project);
}
}
} catch (Exception e) {
/* System.out.println("Failed to resolve the target type ");
System.out.println(e.getStackTrace());*/
continue;
}
if (ArrayLikeUtil.isArrayLike(targetType, project)) {
// System.out.println("processing ArrayLike "+targetType.getQualifiedName());
if (!importAdded) {
if (searchScope.getImports() != null) {
List imports = Arrays.asList(searchScope.getImports());
if (imports.contains(ArrayLikeUtil.ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC_QNAME)) {
importAdded = true;
}
}
if (!importAdded) {
searchScope.addImport(ArrayLikeUtil.ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC_QNAME);
importAdded = true;
}
}
ASProjectScope projectScope = (ASProjectScope) project.getScope();
boolean alreadyUsed = usedIterators.contains(arrIter); //otherwise we need a typed var declaration
if (!alreadyUsed)
usedIterators.add(arrIter); // we can use it again without var declaration in the current scope
IDefinition metaSource = ArrayLikeUtil.resolveArrayLikeDefinitionSource(targetType, project);
IMetaTag arrayLikeTag = ArrayLikeUtil.getArrayLikeMetaData(metaSource);
//change the loop node to: for(arrIter:Object = arrayLike(instance,"{lengthCheck}","{getterCheck}" or (null if getterCheck=="[]"), Boolean(lengthAccess=="method"), Boolean(!isForEach)); arrIter.hasNext();)
// {
// {!alreadyUsed: var} originalName{!alreadyUsed: :OriginalType} = arrIter.getNext();
// {originalBody Loop body}
// }
boolean useDynamicAccess = !project.isStaticTypedTarget();
ForLoopNode.ILoopMutation mutation = ArrayLikeUtil.createArrayLikeLoopMutation((ForLoopNode) loopNode, arrIter, useDynamicAccess);
String generatorFuncName = ArrayLikeUtil.ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC;
IdentifierNode funcName = new IdentifierNode(generatorFuncName);
FunctionCallNode specificIteratorFunc = new FunctionCallNode(funcName);
specificIteratorFunc.getNameNode().setParent(specificIteratorFunc);
//now add the arguments
//use the original iteration target node {from: for each(iteratee in target) }, reparent it to the generator function args as the target 'instance'
specificIteratorFunc.getArgumentsNode().addItem((NodeBase) mutation.getIterationTarget());
specificIteratorFunc.getArgumentsNode().setParent(specificIteratorFunc);
//now add the metadata driven args
//length check:
LiteralNode metaArg = new LiteralNode(ILiteralNode.LiteralType.STRING, wrapQuotes(ArrayLikeUtil.getLengthArg(arrayLikeTag)));
metaArg.setSynthetic(true); //may not be needed
specificIteratorFunc.getArgumentsNode().addItem(metaArg);
//index getter:
String getter = ArrayLikeUtil.getGetterArg(arrayLikeTag);
if (getter.equals(INDEX_ACCESS_UNCHANGED)) {
// pass null for the getter
metaArg = new LiteralNode(ILiteralNode.LiteralType.NULL, "null");
metaArg.setSynthetic(true); //may not be needed
} else {
metaArg = new LiteralNode(ILiteralNode.LiteralType.STRING, wrapQuotes(ArrayLikeUtil.getGetterArg(arrayLikeTag)));
metaArg.setSynthetic(true); //may not be needed
}
specificIteratorFunc.getArgumentsNode().addItem(metaArg);
//boolean indicating true if the length check is a method call, false if reguler 'getter' style:
metaArg = new LiteralNode(ILiteralNode.LiteralType.BOOLEAN, ArrayLikeUtil.getLengthAccessArg(arrayLikeTag).equals("method") ? "true" : "false");
metaArg.setSynthetic(true); //may not be needed
specificIteratorFunc.getArgumentsNode().addItem(metaArg);
//distinguish whether this iterator is values (for each(x in y)) or keys (regular for (x in y))
if (!isForeach) {
//we add an extra boolean true indicating that we only want to iterate over keys (and not values)
metaArg = new LiteralNode(ILiteralNode.LiteralType.BOOLEAN, "true");
metaArg.setSynthetic(true); //may not be needed
specificIteratorFunc.getArgumentsNode().addItem(metaArg);
}
mutation.prepareConditionals(!alreadyUsed, specificIteratorFunc);
mutation.prepareContent();
Collection problems = ((ForLoopNode) loopNode).processMutation(mutation, searchScope);
if (problems.size() > 0) {
project.getProblems().addAll(problems);
}
}
//@todo if the targetType is null we could output an opt-in or opt-out (via config) warning suggesting that Strongly typed for each loop targets are more reliable
//That would help people a lot when porting things to dynamic targets like js. for each(var something:Object in event.target.someField).... is sometimes a problem
/*if (targetType == null){
System.out.println("FOUND NULL FOREACH TARGET TYPE AT "+loopNode.getLine()+","+loopNode.getSourcePath());
} else {
System.out.println("Target type was "+targetType.getQualifiedName() + " at " + loopNode.getLine() +"," +loopNode.getSourcePath());
}*/
}
}
private static int currentProject = -1;
private static HashMap arrayLikeLookups = null;
private static void resetLookupsIfNeeded(ICompilerProject project) {
if (currentProject == project.hashCode()) return;
arrayLikeLookups = new HashMap(20);
currentProject = project.hashCode();
}
/**
*
* @param definition the definition to check
* @return true if this specific definition has the ArrayLike annotation
*/
public static boolean definitionIsArrayLike(IDefinition definition) {
return ((definition instanceof IClassDefinition || definition instanceof IInterfaceDefinition) &&
definition.hasMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE));
}
/**
* Method to check an annotated definition in the current project, and verify that it is valid
* This method poplulates the external problems collection argument with the first observed problem
* @param definition the definition to verify
* @param project the current project
* @param problems a problem list to populate with any detected issues
* @return boolean true if there were no issues, otherwise false
*/
public static boolean validateArrayLikeDefinition(IDefinition definition, ICompilerProject project, List problems) {
IMetaTag arrayLikeTag = definition.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
if (!(project instanceof IRoyaleProject)) {
if (problems != null) {
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "This is not a Royale Project. Only Royale projects support "+IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE));
}
return false;
}
//mandatory
String lengthCheck = arrayLikeTag.getAttributeValue(LENGTH_ARG);
boolean pass = true;
if (lengthCheck == null) {
pass = false;
if (problems != null) {
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "Missing '"+LENGTH_ARG+"' metadata argument"));
}
} //verify further after we check the lengthAccess arg
if (pass) {
String getterCheck = arrayLikeTag.getAttributeValue(GETVALUE_ARG);
if (getterCheck == null) {
pass = false;
if (problems != null) {
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "Missing '"+GETVALUE_ARG+"' metadata argument"));
}
} else {
//check the definition
if (getterCheck != INDEX_ACCESS_UNCHANGED) { //'[]' is a special case where regular Array access is not changed
//@todo extra safety: check the definition has the method that is specified in the metadata, add a problem if not.
}
}
}
String setterCheck = null;
if (pass) {
setterCheck = arrayLikeTag.getAttributeValue(SETVALUE_ARG);
if (setterCheck == null) {
pass = false;
if (problems != null) {
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "Missing '"+SETVALUE_ARG+"' metadata argument"));
}
} else {
//check the definition
if (setterCheck != INDEX_ACCESS_UNCHANGED) {//'[]' is a special case where regular Array access is not changed
//@todo extra safety: check the definition has the method that is specified in the metadata, add a problem if not.
}
}
}
if (pass) {
if (!setterCheck.equals(INDEX_ACCESS_UNCHANGED)) {//otherwise we can ignore the setter arg sequence, even if it is (incorrectly) specified.
String setterSequenceCheck = arrayLikeTag.getAttributeValue(SETTER_ARG_SEQUENCE_ARG);
if (setterSequenceCheck == null) {
setterSequenceCheck = SETTER_ARG_SEQUENCE_DEFAULT;
}
if (!setterSequenceCheck.equals(SETTER_ARG_SEQUENCE_VALUE_INDEX) && !setterSequenceCheck.equals(SETTER_ARG_SEQUENCE_INDEX_VALUE)) {
pass = false;
if (problems != null) {
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "Missing '"+SETVALUE_ARG+"' metadata argument"));
}
} else {
//check the definition
//@todo extra safety: check the definition has the method signature that is specified in the metadata, add a problem if not.
//the index argument should be a numeric type, and the specified method should have the two arguments
}
}
}
if (pass) {
//can be method or getter, defaults to getter
String lengthAccess = arrayLikeTag.getAttributeValue(LENGTH_ACCESS_ARG);
if (lengthAccess == null) lengthAccess = LENGTH_ACCESS_DEFAULT;
//only "getter" or "method" are allowed options
if (!lengthAccess.equals(LENGTH_ACCESS_GETTER) && !lengthAccess.equals(LENGTH_ACCESS_METHOD)) {
pass = false;
if (problems != null) {
//problems.add(new ArrayLikeConfigurationErrorProblem(definition.getNode(), "Metadata argument for 'lengthAccess' missing or invalid"));
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "Metadata argument for '"+LENGTH_ACCESS_ARG+"' missing or invalid"));
}
} else {
//@todo extra safety: check the definition has the length getter or method that is specified that is specified in the metadata, add a problem if not.
}
}
if (pass) {
ASProjectScope projectScope = ((ASProjectScope)project.getScope());
IDefinition def = projectScope.findDefinitionByName(ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC_QNAME);
if (!(def instanceof FunctionDefinition)) {
//@todo extra safety: this may not be sufficient to verify a concrete reference
problems.add(new ArrayLikeConfigurationErrorProblem(arrayLikeTag, "The RoyaleArrayLike Tag is valid, but there is a missing concrete reference in the project to: "+ARRAYLIKE_GENERIC_SUPPORT_ITERATOR_FACTORY_FUNC_QNAME+ " (which is required)"));
pass = false;
}
}
return pass;
}
/**
* Checks whether an instance is ArrayLike at a usage site.
* This is typically either array access or assignment, or for the target of a 'for each' loop
* @param definition the definition check to verify if an instance is ArrayLike at a usage site
* @return true if this definition either has its own ArrayLike annotation, or inherits via its ancestors or interfaces
*/
synchronized public static boolean isArrayLike(IDefinition definition, ICompilerProject project) {
if (definition != null && project instanceof IRoyaleProject) {
resetLookupsIfNeeded(project);
if (definition instanceof IClassDefinition) {
String qName = definition.getQualifiedName();
if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
return checkClass((IClassDefinition) definition, (IRoyaleProject) project);
} else if (definition instanceof IInterfaceDefinition) {
String qName = definition.getQualifiedName();
if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
return checkInterface((IInterfaceDefinition) definition, (IRoyaleProject) project);
}
}
return false;
}
private static boolean checkClass(IClassDefinition definition, IRoyaleProject project){
String qName = definition.getQualifiedName();
resetLookupsIfNeeded(project);
if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
boolean isArrayLike = false;
//check the class
isArrayLike = definition.hasMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
String originalQName = qName;
if (!isArrayLike) {
//check this definition's interfaces and their ancestry
Set interfaces = definition.resolveAllInterfaces(project);
for (IInterfaceDefinition interfaceDef:interfaces) {
isArrayLike = checkInterface(interfaceDef, project);
if (isArrayLike) {
qName = arrayLikeLookups.get(interfaceDef.getQualifiedName());
break;
}
}
//check this definition's parent class (recursive check)
if (!isArrayLike) {
IClassDefinition baseClassDef = definition.resolveBaseClass(project);
if (baseClassDef != null) {
isArrayLike = checkClass(baseClassDef, project);
if (isArrayLike) {
//we need to share the same lookup that was used when checking the parent chain
qName = arrayLikeLookups.get(baseClassDef.getQualifiedName());
}
}
}
}
arrayLikeLookups.put(originalQName, isArrayLike ? qName : null);
return isArrayLike;
}
private static boolean checkInterface(IInterfaceDefinition interfaceDefinition, IRoyaleProject project){
String qName = interfaceDefinition.getQualifiedName();
resetLookupsIfNeeded(project);
if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
boolean isArrayLike = interfaceDefinition.hasMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
String originalQName = qName;
if (!isArrayLike) qName = null;
ArrayList ancestry = null;
if (!isArrayLike) {
// if we find an ancestor in the interface extension chain, we can cache the lookups back to it as the source of ArrayLike data
ancestry = new ArrayList();
Iterator interfaceIterator = interfaceDefinition.interfaceIterator(project, false);
while (interfaceIterator.hasNext()) {
interfaceDefinition = interfaceIterator.next();
qName = interfaceDefinition.getQualifiedName();
if (arrayLikeLookups.containsKey(qName) ){
//we don't have to keep going, we now already know the answer
qName = arrayLikeLookups.get(qName); //this will either be null or the correct mapping to the ArrayLike source definition qName
break;
}
ancestry.add(qName);
isArrayLike = interfaceDefinition.hasMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
if (isArrayLike) break;
qName = null;
}
}
if (ancestry != null) {
//process ancestry lookups
for (String ancestorQName: ancestry) arrayLikeLookups.put(ancestorQName, qName);
}
arrayLikeLookups.put(originalQName, qName);
return isArrayLike;
}
public static IDefinition resolveArrayLikeDefinitionSource(IDefinition sourceDefinition, ICompilerProject project){
IDefinition metaSource = null;
if (sourceDefinition != null){
resetLookupsIfNeeded(project);
String sourceDefinitionQName = sourceDefinition.getQualifiedName();
if (arrayLikeLookups.containsKey(sourceDefinitionQName)) {
String resolvedQName = arrayLikeLookups.get(sourceDefinitionQName);
if (!sourceDefinitionQName.equals(resolvedQName)) {
metaSource = project.resolveQNameToDefinition(resolvedQName);
} else {
metaSource = sourceDefinition;
}
}
}
return metaSource;
}
/**
* Before calling the following methods, isArrayLike should be verified, and the source of the metadata should be used
* via resolveArrayLikeDefinitionSource
* @param definition
* @return
*/
public static IMetaTag getArrayLikeMetaData(IDefinition definition){
return definition.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
}
public static String getLengthArg(IMetaTag arrayLikeTag) {
return arrayLikeTag.getAttributeValue(LENGTH_ARG);
}
public static String getGetterArg(IMetaTag arrayLikeTag) {
return arrayLikeTag.getAttributeValue(GETVALUE_ARG);
}
public static String getSetterArg(IMetaTag arrayLikeTag) {
return arrayLikeTag.getAttributeValue(SETVALUE_ARG);
}
public static String getSetterArgSequenceArg(IMetaTag arrayLikeTag) {
String val = arrayLikeTag.getAttributeValue(SETTER_ARG_SEQUENCE_ARG);
if (val == null) val = SETTER_ARG_SEQUENCE_DEFAULT;
return val;
}
public static String getLengthAccessArg(IMetaTag arrayLikeTag) {
String val = arrayLikeTag.getAttributeValue(LENGTH_ACCESS_ARG);
if (val == null) val = LENGTH_ACCESS_DEFAULT;
return val;
}
public static ArrayLikeLoopMutation createArrayLikeLoopMutation(ForLoopNode loopNode, String arrIterName, boolean useDynamicAccess) {
return new ArrayLikeLoopMutation(loopNode, arrIterName, useDynamicAccess);
}
}
/**
* Support for mutating a 'for each' loop with the following signature
* for each(var iteratee:IterateeType in someInstance){ //swf has implicit cast here.
* {loop body}
* }
*
* into the following style of loop:
* for(var iteratorVarName:Object=arraylike(someInstance, { plus metadata driven data here}); iteratorVarName.hasNext(); ) {
* var iteratee:IterateeType = iteratorVarName.getNext(); //with implicit cast here
* {loop body}
* }
* someInstance is a class annotated with [RoyaleArrayLike] metadata that determines its eligibility for applying this change
* and also the specific data to pass to the arrayLike function which serves as an 'iterator factory' for the above 'for each' support
* An example would be [RoyaleArrayLike(getValue="getItemAt",setValue="setItemAt",setterArgSequence="item,index",length="length",lengthAccess="getter")]
* (The setValue and setterArgSequence metatag params are not needed in terms of for-each support... they, along with the getValue arg are to support
* transforming Array Access via [numericKey] Dynamic access to method call access for setting and getting values)
* This only works when the compiler knows the type of 'someInstance', otherwise it cannot check to see if it has an ArrayLike metatag
*/
class ArrayLikeLoopMutation implements ForLoopNode.ILoopMutation {
/**
* Constructor.
*/
public ArrayLikeLoopMutation(ForLoopNode target, String iteratorVarName, boolean useDynamicAccess)
{
super();
this.target = target;
this.iteratorVarName = iteratorVarName;
this.useDynamicAccess = useDynamicAccess;
}
final private IForLoopNode.ForLoopKind kind = IForLoopNode.ForLoopKind.FOR;
private boolean useDynamicAccess;
private String iteratorVarName;
private NodeBase iteratee;
private FunctionCallNode iterateeValueNode;
private ForLoopNode target;
private ArrayList analyzeRequests;
private ContainerNode mutatedConditionals = new ContainerNode();
public boolean isValid(){
return mutatedConditionals !=null
&& mutatedConditionals.getChildCount() == 3
&& iteratee != null;
}
@Override
public List getAnalyzeRequests() {
return analyzeRequests;
}
private FunctionCallNode createIteratorCall(String methodName){
IdentifierNode leftOp = new IdentifierNode(this.iteratorVarName);
ExpressionNodeBase nameNode;
if (useDynamicAccess) {
//this is the easiest way to keep things lightweight for JS without needing an actual iterator class or interface
//use iterator['hasNext']()/iterator['getNext']() etc
LiteralNode rightOp = new LiteralNode(ILiteralNode.LiteralType.STRING, "'"+methodName+"'");
DynamicAccessNode dynNode = new DynamicAccessNode(leftOp);
dynNode.setRightOperandNode(rightOp);
rightOp.setParent(dynNode);
leftOp.setParent(dynNode);
nameNode = dynNode;
} else {
IdentifierNode rightOp = new IdentifierNode(methodName);
nameNode = new MemberAccessExpressionNode(leftOp,null, rightOp);
leftOp.setParent(nameNode);
rightOp.setParent(nameNode);
}
FunctionCallNode functionCallNode = new FunctionCallNode(nameNode);
nameNode.setParent(functionCallNode);
functionCallNode.getArgumentsNode().setParent(functionCallNode);
return functionCallNode;
}
private NodeBase setupIterator(boolean firstUse, FunctionCallNode assignedValue){
IdentifierNode varName = new IdentifierNode(this.iteratorVarName);
NodeBase setupNode;
if (firstUse) {
//create something like: var iteratorName:Object = {functionCallNode}
IdentifierNode typeNode = new IdentifierNode(IASLanguageConstants.Object);
VariableNode varNode = new VariableNode(varName, typeNode);
varNode.setKeyword(new ASToken(ASToken.TOKEN_KEYWORD_VAR, -1,-1,-1,-1,"var"));
varNode.getKeywordNode().setParent(varNode);
varNode.setAssignedValue(null, assignedValue);
//explicitly set parent <--> child relationships
varNode.addChild(varNode.getKeywordNode());
varNode.addChild(varName);
varNode.addChild(typeNode);
varNode.addChild(assignedValue);
setupNode = new VariableExpressionNode(varNode);
} else {
//recycledIteratorName = {functionCallNode}
ASToken assignOp = new ASToken(ASTokenTypes.TOKEN_OPERATOR_ASSIGNMENT,-1,-1,-1,-1,"=");
setupNode = new BinaryOperatorAssignmentNode(assignOp, varName, assignedValue);
//explicitly set parent <--> child relationships
varName.setParent(setupNode);
assignedValue.setParent(setupNode);
}
return setupNode;
}
private NodeBase setupIteratee(){
//original part of conditionals group should be either a VariableExpressionNode or an IdentifierNode
IExpressionNode original = ((BinaryOperatorInNode)(target.getConditionalExpressionNodes()[0])).getLeftOperandNode();
NodeBase setupNode;
FunctionCallNode assignedValue = createIteratorCall(ArrayLikeUtil.ARRAYLIKE_GET_NEXT);
if (original instanceof VariableExpressionNode) {
VariableNode varNode = (VariableNode)((VariableExpressionNode) original).getTargetVariable();
//add the assigned value
varNode.setAssignedValue(null, assignedValue);
assignedValue.setParent(varNode);
setupNode = varNode;
} else if (original instanceof IdentifierNode) {
ASToken assignOp = new ASToken(ASTokenTypes.TOKEN_OPERATOR_ASSIGNMENT,-1,-1,-1,-1,"=");
BinaryOperatorAssignmentNode assignmentNode = new BinaryOperatorAssignmentNode(assignOp,(IdentifierNode)original, assignedValue);
((IdentifierNode) original).setParent(assignmentNode);
assignedValue.setParent(assignmentNode);
setupNode = assignmentNode;
} else {
setupNode = null;
//System.out.println("setupIteratee::UNEXPECTED");
}
if (setupNode != null) iterateeValueNode = assignedValue; //so we can run analyze on only the new content 'assignedValue'
return setupNode;
}
public ForLoopNode getLoopTarget(){
return target;
}
public IExpressionNode getIterationTarget(){
//don't use copy/clone
return ((BinaryOperatorInNode)(target.getConditionalExpressionNodes()[0])).getRightOperandNode();
}
public void prepareConditionals(boolean firstUse, FunctionCallNode factoryFuncCall){
assert !conditionalsChanged : "populateConditionals was already called";
//1st part of the substituted for(;;) loop
mutatedConditionals.addItem(setupIterator(firstUse, factoryFuncCall));
//2nd part of the substituted for(;;) loop
mutatedConditionals.addItem(createIteratorCall(ArrayLikeUtil.ARRAYLIKE_HAS_NEXT));
//3rd part of the substituted for(;;) loop is a NilNode in this case
mutatedConditionals.addItem(new NilNode());
conditionalsChanged = true;
}
public void prepareContent(){
assert !contentsChanged : "populateContent was already called";
iteratee = setupIteratee();
contentsChanged = iteratee != null;
}
public void processConditionals(ASScope scope, Collection problems){
ContainerNode conditionals = target.getConditionalsContainerNode();
conditionals.removeAllChildren();
conditionals.addChild((NodeBase) mutatedConditionals.getChild(0),0);
conditionals.addChild((NodeBase) mutatedConditionals.getChild(1),1);
conditionals.addChild((NodeBase) mutatedConditionals.getChild(2),2);
if (analyzeRequests == null) {
analyzeRequests = new ArrayList();
}
for (int i=0;i<3;i++) {
analyzeRequests.add((NodeBase) conditionals.getChild(i));
}
}
public void processContents(ASScope scope, Collection problems){
BlockNode block = target.getContentsNode();
//'insert' at position 0 to prepend this to the original loop's block content
block.addChild(iteratee, 0);
//if a) the child count was one previously [it is now 2] *and* b) it was an implicit Block
if (block.getChildCount() == 2 && block.getContainerType() == IContainerNode.ContainerType.IMPLICIT) {
//we set it as 'explicit' even though it is not. This is particularly helpful in implementations like compiler-jx because it
//provides a clue for expression of the loop content with explicit block braces, instead of as it is in the original
//code where they were not needed. For swf bytecode expression this change is not needed, but also has no downside.
block.setContainerType(IContainerNode.ContainerType.BRACES);
}
EnumSet set = EnumSet.of(
PostProcessStep.POPULATE_SCOPE);
//only add the iteratee to the analysis. re-evaluating the original variable node (if it is not a pre-existing identifier)
//would add it to the scope's definition store a second time, and we don't want that (probably no harm, but it would add a warning for duplicate variable declaration).
if (analyzeRequests == null) {
analyzeRequests = new ArrayList();
}
analyzeRequests.add(iterateeValueNode);
}
@Override
public IForLoopNode.ForLoopKind getKind() {
return kind;
}
private boolean conditionalsChanged;
@Override
public boolean mutatesConditionals() {
return conditionalsChanged;
}
private boolean contentsChanged;
@Override
public boolean mutatesContents() {
return contentsChanged;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy