You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by gr...@apache.org on 2019/10/02 08:39:22 UTC
[royale-compiler] 01/03: RoyaleArrayLike support
This is an automated email from the ASF dual-hosted git repository.
gregdove pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
commit 9c0ada6e30f1675f7137a76d3b76f718b5f9f87d
Author: greg-dove <gr...@gmail.com>
AuthorDate: Sun Sep 29 15:09:10 2019 +1300
RoyaleArrayLike support
---
.../ArrayLikeConfigurationErrorProblem.java | 54 ++
.../problems/ArrayLikeUsageErrorProblem.java | 48 ++
compiler-jx/pom.xml | 2 +-
.../internal/codegen/js/jx/ForEachEmitter.java | 35 +-
.../internal/codegen/js/jx/MethodEmitter.java | 4 +-
.../js/sourcemaps/TestSourceMapStatements.java | 3 +-
.../constants/IMetaAttributeConstants.java | 3 +
.../compiler/internal/as/codegen/ABCGenerator.java | 2 +
.../as/codegen/ClassDirectiveProcessor.java | 68 +-
.../as/codegen/GlobalDirectiveProcessor.java | 2 +
.../as/codegen/InterfaceDirectiveProcessor.java | 9 +
.../projects/RoyaleProjectConfigurator.java | 2 +-
.../royale/compiler/internal/scopes/ASScope.java | 18 +-
.../compiler/internal/tree/as/ForLoopNode.java | 56 +-
.../compiler/internal/tree/as/FunctionNode.java | 2 +-
.../royale/compiler/internal/tree/as/TreeNode.java | 7 +-
.../main/java/org/apache/royale/utils/ASTUtil.java | 43 +
.../org/apache/royale/utils/ArrayLikeUtil.java | 900 +++++++++++++++++++++
18 files changed, 1188 insertions(+), 70 deletions(-)
diff --git a/compiler-common/src/main/java/org/apache/royale/compiler/problems/ArrayLikeConfigurationErrorProblem.java b/compiler-common/src/main/java/org/apache/royale/compiler/problems/ArrayLikeConfigurationErrorProblem.java
new file mode 100644
index 0000000..1229c46
--- /dev/null
+++ b/compiler-common/src/main/java/org/apache/royale/compiler/problems/ArrayLikeConfigurationErrorProblem.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * 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.problems;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.problems.annotations.DefaultSeverity;
+
+/**
+ * When a class or interface is annotated with [ArrayLike] metadata, it needs
+ * to provide the required minimum set of arguments to allow instances of the class
+ * to be treated as 'ArrayLike' when encountered in other source code.
+ * It also requires the concrete presence of the arrayLike iterator factory as a separate
+ * stand-alone dependency in the current project level compilation. This ensures that there
+ * is no dependency missing if the ArrayLike definition is in an external library: the supporting
+ * definition for iterator support must be in the same library
+ * (which might mean it is repeated across different libraries, but that we can be sure it is available)
+ */
+@DefaultSeverity(CompilerProblemSeverity.ERROR)
+public class ArrayLikeConfigurationErrorProblem extends CompilerProblem
+{
+ public static final String DESCRIPTION =
+ "${description}. ArrayLike definition has an error with a required argument or dependency.";
+
+ public static final int errorCode = 9999;
+
+ public ArrayLikeConfigurationErrorProblem(ISourceLocation site, String description)
+ {
+ super(site);
+ this.description = description;
+ }
+
+ public ArrayLikeConfigurationErrorProblem(String sourcePath, int start, int end, int line, int column, int endLine, int endColumn, String description)
+ {
+ super(sourcePath, start, end, line, column, endLine, endColumn);
+ this.description = description;
+ }
+ public final String description;
+}
diff --git a/compiler-common/src/main/java/org/apache/royale/compiler/problems/ArrayLikeUsageErrorProblem.java b/compiler-common/src/main/java/org/apache/royale/compiler/problems/ArrayLikeUsageErrorProblem.java
new file mode 100644
index 0000000..51320e3
--- /dev/null
+++ b/compiler-common/src/main/java/org/apache/royale/compiler/problems/ArrayLikeUsageErrorProblem.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * 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.problems;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+import org.apache.royale.compiler.problems.annotations.DefaultSeverity;
+
+/**
+ * When an instance of an ArrayLike class is used in an unsupported way, an ArrayLikeUsageErrorProblem
+ * is created. It needs to be addressed in the source code at the usage site.
+ **/
+@DefaultSeverity(CompilerProblemSeverity.ERROR)
+public class ArrayLikeUsageErrorProblem extends CompilerProblem
+{
+ public static final String DESCRIPTION =
+ "${description}. ArrayLike usage is not valid.";
+
+ public static final int errorCode = 9998;
+
+ public ArrayLikeUsageErrorProblem(ISourceLocation site, String description)
+ {
+ super(site);
+ this.description = description;
+ }
+
+ public ArrayLikeUsageErrorProblem(String sourcePath, int start, int end, int line, int column, int endLine, int endColumn, String description)
+ {
+ super(sourcePath, start, end, line, column, endLine, endColumn);
+ this.description = description;
+ }
+ public final String description;
+}
diff --git a/compiler-jx/pom.xml b/compiler-jx/pom.xml
index d1780ef..d9d9458 100644
--- a/compiler-jx/pom.xml
+++ b/compiler-jx/pom.xml
@@ -86,7 +86,7 @@
<excludes>
<exclude>**/TestRoyaleFile.java</exclude>
<exclude>**/TestRoyaleMXML*.java</exclude>
- <exclude>**/TestSourceMap*.java</exclude>
+ <exclude>**/TestSourceMapMXML*.java</exclude>
<exclude>**/TestMXML*.java</exclude>
<exclude>**/TestRoyaleClass.java</exclude>
<exclude>**/TestTypedefsCompile.java</exclude>
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForEachEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForEachEmitter.java
index 86e6227..0def4ce 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForEachEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForEachEmitter.java
@@ -35,7 +35,6 @@ import org.apache.royale.compiler.tree.as.IBinaryOperatorNode;
import org.apache.royale.compiler.tree.as.IContainerNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IForLoopNode;
-import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IVariableExpressionNode;
import org.apache.royale.compiler.tree.as.IVariableNode;
@@ -117,6 +116,15 @@ public class ForEachEmitter extends JSSubEmitter implements
isProxy = true;
}
}
+ else if (obj.getNodeID() == ASTNodeID.Op_DescendantsID)
+ {
+ //it should always be XMLList... but check anyway
+ if (((JSRoyaleEmitter)getEmitter()).isXMLList((MemberAccessExpressionNode)obj))
+ {
+ write(".elementNames()");
+ isXML = true;
+ }
+ }
else if (obj.getNodeID() == ASTNodeID.MemberAccessExpressionID)
{
if (((JSRoyaleEmitter)getEmitter()).isXMLList((MemberAccessExpressionNode)obj))
@@ -164,19 +172,28 @@ public class ForEachEmitter extends JSSubEmitter implements
writeNewline();
write(ASEmitterTokens.BLOCK_OPEN);
writeNewline();
- startMapping(childNode);
+
if (childNode instanceof IVariableExpressionNode)
{
+ startMapping(childNode);
write(ASEmitterTokens.VAR);
write(ASEmitterTokens.SPACE);
- write(((IVariableNode) childNode.getChild(0)).getName());
+ write(((IVariableNode) childNode.getChild(0)).getName()); //it's always a local var
+ //putting this in here instead of common code following the 2 blocks to keep sourcemap tests passing
+ write(ASEmitterTokens.SPACE);
+ write(ASEmitterTokens.EQUAL);
+ write(ASEmitterTokens.SPACE);
+ endMapping(childNode);
}
- else
- write(((IIdentifierNode) childNode).getName());
- write(ASEmitterTokens.SPACE);
- write(ASEmitterTokens.EQUAL);
- write(ASEmitterTokens.SPACE);
- endMapping(childNode);
+ else { //IdentifierNode
+ getWalker().walk(childNode); //we need to walk here, to deal with non-local var identifiers
+ startMapping(childNode);
+ write(ASEmitterTokens.SPACE);
+ write(ASEmitterTokens.EQUAL);
+ write(ASEmitterTokens.SPACE);
+ endMapping(childNode);
+ }
+
startMapping(rnode);
write(targetName);
if (isXML)
diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/MethodEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/MethodEmitter.java
index 0fc7b0b..688b190 100644
--- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/MethodEmitter.java
+++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/MethodEmitter.java
@@ -40,6 +40,7 @@ import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.as.IClassNode;
import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.utils.ASTUtil;
public class MethodEmitter extends JSSubEmitter implements
ISubEmitter<IFunctionNode>
@@ -63,7 +64,8 @@ public class MethodEmitter extends JSSubEmitter implements
ICompilerProject project = getWalker().getProject();
fjs.getDocEmitter().emitMethodDoc(node, project);
-
+ ASTUtil.processFunctionNode(fn, project, true);
+
boolean isConstructor = node.isConstructor();
boolean addingBindableImplementsSupport = isConstructor &&
diff --git a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java
index d13a577..0e306d1 100644
--- a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java
+++ b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/js/sourcemaps/TestSourceMapStatements.java
@@ -291,7 +291,8 @@ public class TestSourceMapStatements extends SourceMapTestBase
assertMapping(node, 0, 14, 1, 5, 1, 21); // var foreachiter0
assertMapping(node, 0, 14, 1, 25, 1, 44); // foreachiter0_target
assertMapping(node, 0, 17, 1, 44, 1, 46); // )
- assertMapping(node, 0, 9, 3, 0, 3, 4); // i =
+ assertMapping(node, 0, 9, 3, 0, 3, 1); // i
+ assertMapping(node, 0, 9, 3, 1, 3, 4); // =
assertMapping(node, 0, 14, 3, 4, 3, 38); // foreachiter0_target[foreachiter0];
assertMapping(node, 0, 20, 5, 2, 5, 7); // break
assertMapping(node, 0, 25, 5, 7, 5, 8); // ;
diff --git a/compiler/src/main/java/org/apache/royale/compiler/constants/IMetaAttributeConstants.java b/compiler/src/main/java/org/apache/royale/compiler/constants/IMetaAttributeConstants.java
index 4aba20b..ecf8158 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/constants/IMetaAttributeConstants.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/constants/IMetaAttributeConstants.java
@@ -216,6 +216,9 @@ public interface IMetaAttributeConstants
// [RoyalePrivateConstructor]
static final String ATTRIBUTE_PRIVATE_CONSTRUCTOR = "RoyalePrivateConstructor";
+
+ // [RoyaleArrayLike(...args)]
+ static final String ATTRIBUTE_ARRAYLIKE = "RoyaleArrayLike";
/**
* List of metadata tags that do not inherit
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ABCGenerator.java b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ABCGenerator.java
index 0772f61..10e47d8 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ABCGenerator.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ABCGenerator.java
@@ -68,6 +68,7 @@ import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.tree.as.IParameterNode;
import org.apache.royale.compiler.units.requests.IABCBytesRequestResult;
import com.google.common.util.concurrent.Futures;
+import org.apache.royale.utils.ASTUtil;
/**
* ABCGenerator is the public interface to the code generator.
@@ -713,6 +714,7 @@ public class ABCGenerator implements ICodeGenerator
{
assert !methodInfo.isNative() : "Native methods should be handled in the main thread and not be dispatched to a background thread!";
functionNode.parseFunctionBody(enclosingScope.getProblems());
+ ASTUtil.processFunctionNode(functionNode, enclosingScope.getProject(), false);
generateMethodBodyForFunction(deferredVisitEnds, methodInfo, functionNode, enclosingScope, null);
}
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ClassDirectiveProcessor.java b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ClassDirectiveProcessor.java
index 16cfa1e..b8303ff 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ClassDirectiveProcessor.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/ClassDirectiveProcessor.java
@@ -21,10 +21,7 @@ package org.apache.royale.compiler.internal.as.codegen;
import static org.apache.royale.abc.ABCConstants.*;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.instructionlist.InstructionList;
@@ -51,48 +48,16 @@ import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.constants.INamespaceConstants;
-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.IInterfaceDefinition;
-import org.apache.royale.compiler.definitions.INamespaceDefinition;
+import org.apache.royale.compiler.definitions.*;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.exceptions.CodegenInterruptedException;
-import org.apache.royale.compiler.problems.AbstractOutsideClassProblem;
-import org.apache.royale.compiler.problems.BadAccessAbstractMethodProblem;
-import org.apache.royale.compiler.problems.CircularTypeReferenceProblem;
-import org.apache.royale.compiler.problems.ConstructorCannotHaveReturnTypeProblem;
-import org.apache.royale.compiler.problems.ConstructorIsGetterSetterProblem;
-import org.apache.royale.compiler.problems.ConstructorIsStaticProblem;
-import org.apache.royale.compiler.problems.ConstructorMustBePublicOrPrivateProblem;
-import org.apache.royale.compiler.problems.ConstructorMustBePublicProblem;
-import org.apache.royale.compiler.problems.DuplicateClassDefinitionProblem;
-import org.apache.royale.compiler.problems.DynamicNotOnClassProblem;
-import org.apache.royale.compiler.problems.FinalOutsideClassProblem;
-import org.apache.royale.compiler.problems.ForwardReferenceToBaseClassProblem;
-import org.apache.royale.compiler.problems.FunctionNotMarkedOverrideProblem;
-import org.apache.royale.compiler.problems.ICompilerProblem;
-import org.apache.royale.compiler.problems.IncompatibleOverrideProblem;
-import org.apache.royale.compiler.problems.InvalidOverrideProblem;
-import org.apache.royale.compiler.problems.MultipleContructorDefinitionsProblem;
-import org.apache.royale.compiler.problems.NativeVariableProblem;
-import org.apache.royale.compiler.problems.OverrideFinalProblem;
-import org.apache.royale.compiler.problems.OverrideNotFoundProblem;
-import org.apache.royale.compiler.problems.StaticAndOverrideProblem;
-import org.apache.royale.compiler.problems.StaticNamespaceDefinitionProblem;
-import org.apache.royale.compiler.problems.SyntaxProblem;
-import org.apache.royale.compiler.problems.VirtualOutsideClassProblem;
+import org.apache.royale.compiler.internal.tree.as.*;
+import org.apache.royale.compiler.problems.*;
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.ICommonClassNode;
-import org.apache.royale.compiler.tree.as.IDefinitionNode;
-import org.apache.royale.compiler.tree.as.IExpressionNode;
-import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode;
-import org.apache.royale.compiler.tree.as.INamespaceDecorationNode;
+import org.apache.royale.compiler.tree.as.*;
import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind;
import org.apache.royale.compiler.internal.abc.FunctionGeneratorHelper;
import org.apache.royale.compiler.internal.as.codegen.ICodeGenerator.IConstantValue;
@@ -112,13 +77,8 @@ import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.semantics.MethodBodySemanticChecker;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
-import org.apache.royale.compiler.internal.tree.as.BaseDefinitionNode;
-import org.apache.royale.compiler.internal.tree.as.ClassNode;
-import org.apache.royale.compiler.internal.tree.as.FunctionNode;
-import org.apache.royale.compiler.internal.tree.as.ImportNode;
-import org.apache.royale.compiler.internal.tree.as.NamespaceIdentifierNode;
-import org.apache.royale.compiler.internal.tree.as.NamespaceNode;
-import org.apache.royale.compiler.internal.tree.as.VariableNode;
+import org.apache.royale.utils.ASTUtil;
+import org.apache.royale.utils.ArrayLikeUtil;
/**
* A ClassDirectiveProcessor generates an ABC class
@@ -620,8 +580,16 @@ class ClassDirectiveProcessor extends DirectiveProcessor
Integer.toString(ctorDef.getNameStart()), true);
if (metaTag != null)
metaTags = MetaTag.addMetaTag(metaTags, metaTag);
- }
-
+ }
+
+ if (ArrayLikeUtil.definitionIsArrayLike(classDefinition)) {
+ ArrayList<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
+ boolean valid = ArrayLikeUtil.validateArrayLikeDefinition(classDefinition, classScope.getProject(), problems);
+ if (!valid){
+ classScope.getProject().getProblems().addAll(problems);
+ }
+ }
+
this.classScope.processMetadata(tv, metaTags);
tv.visitEnd();
@@ -816,7 +784,7 @@ class ClassDirectiveProcessor extends DirectiveProcessor
final boolean is_constructor = func.isConstructor();
ICompilerProject project = classScope.getProject();
-
+ ASTUtil.processFunctionNode(func, project, false);
boolean isBindable = false;
if (funcDef instanceof AccessorDefinition)
{
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/GlobalDirectiveProcessor.java b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/GlobalDirectiveProcessor.java
index 7f28031..b9d2013 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/GlobalDirectiveProcessor.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/GlobalDirectiveProcessor.java
@@ -60,6 +60,7 @@ import org.apache.royale.compiler.problems.SyntaxProblem;
import org.apache.royale.compiler.problems.VirtualOutsideClassProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.mxml.IMXMLDocumentNode;
+import org.apache.royale.utils.ASTUtil;
import java.util.LinkedList;
import java.util.List;
@@ -156,6 +157,7 @@ class GlobalDirectiveProcessor extends DirectiveProcessor
else
{
f.parseFunctionBody(currentScope.getProblems());
+ ASTUtil.processFunctionNode(f, currentScope.getProject(), false);
return currentScope.getGenerator().generateFunction(f, this.currentScope, null, null);
}
}
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/InterfaceDirectiveProcessor.java b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/InterfaceDirectiveProcessor.java
index e8409c3..5a47d93 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/InterfaceDirectiveProcessor.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/as/codegen/InterfaceDirectiveProcessor.java
@@ -19,6 +19,7 @@
package org.apache.royale.compiler.internal.as.codegen;
+import java.util.ArrayList;
import java.util.Iterator;
import org.apache.royale.abc.ABCConstants;
@@ -80,6 +81,7 @@ import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.INamespaceDecorationNode;
+import org.apache.royale.utils.ArrayLikeUtil;
/**
* The InterfaceDirectiveProcessor translates an InterfaceNode AST
@@ -252,6 +254,13 @@ public class InterfaceDirectiveProcessor extends DirectiveProcessor
setup_insns.addInstruction(OP_initproperty, interfaceName);
ITraitVisitor tv = this.interfaceScope.getGlobalScope().traitsVisitor.visitClassTrait(TRAIT_Class, interfaceName, 0, cinfo);
+ if (ArrayLikeUtil.definitionIsArrayLike(interfDef)) {
+ ArrayList<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
+ boolean valid = ArrayLikeUtil.validateArrayLikeDefinition(interfDef, this.interfaceScope.getProject(), problems);
+ if (!valid){
+ this.interfaceScope.getProject().getProblems().addAll(problems);
+ }
+ }
this.interfaceScope.processMetadata(tv, interfDef.getAllMetaTags());
tv.visitEnd();
}
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProjectConfigurator.java b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProjectConfigurator.java
index ef8c367..b8a34be 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProjectConfigurator.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProjectConfigurator.java
@@ -58,7 +58,7 @@ public class RoyaleProjectConfigurator extends Configurator
/**
* These imports will eventually come from royale-config.xml.
* <p>
- * Note taht we include AIR-specific imports even for non-AIR projects.
+ * Note that we include AIR-specific imports even for non-AIR projects.
* This does not cause compiler warnings about unknown imports
* in non-AIR projects because the implicit imports don't get
* semantically checked.
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
index b229304..3893bb4 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
@@ -52,6 +52,7 @@ import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IDefinitionSet;
+import org.apache.royale.compiler.tree.as.IForLoopNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.workspaces.IWorkspace;
@@ -69,6 +70,8 @@ public abstract class ASScope extends ASScopeBase
protected static final NamespaceDefinition.IUseNamespaceDirective[] EMPTY_USE_ARRAY =
new NamespaceDefinition.IUseNamespaceDirective[0];
+
+ private static IForLoopNode[] EMPTY_FOREACH_ARRAY = new IForLoopNode[0];
/**
* Constructor
@@ -122,7 +125,20 @@ public abstract class ASScope extends ASScopeBase
private NamespaceDefinition.INamespaceDirective lastNamespaceDirective;
private boolean inWith = false;
-
+
+ private Object forEachs = null;
+ public boolean getHasForEach(){
+ return forEachs != null;
+ }
+ public void addForEach(IForLoopNode value){
+ if (forEachs == null) forEachs = CheapArray.create(1);
+ CheapArray.add(value, forEachs);
+ }
+ public IForLoopNode[] getForEachs(boolean remove){
+ IForLoopNode[] returnForEachs = (IForLoopNode[]) CheapArray.toArray(forEachs, EMPTY_FOREACH_ARRAY);
+ if (remove) forEachs = null;
+ return returnForEachs;
+ }
/**
* Sets the scope which lexically contains this scope.
*
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/ForLoopNode.java b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/ForLoopNode.java
index 8d01661..9a58bcd 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/ForLoopNode.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/ForLoopNode.java
@@ -16,12 +16,12 @@
* limitations under the License.
*
*/
-
package org.apache.royale.compiler.internal.tree.as;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
+import java.util.List;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
@@ -34,6 +34,24 @@ import org.apache.royale.compiler.tree.as.IForLoopNode;
public class ForLoopNode extends FixedChildrenNode implements IForLoopNode
{
+
+ /**
+ * Support for an externally defined loop mutation (AST manipulation)
+ */
+ public static interface ILoopMutation{
+ IForLoopNode.ForLoopKind getKind();
+ IForLoopNode getLoopTarget();
+ boolean mutatesConditionals();
+ boolean mutatesContents();
+ boolean isValid();
+ IExpressionNode getIterationTarget();
+ List<NodeBase> getAnalyzeRequests();
+ void prepareConditionals(boolean firstUse, FunctionCallNode factoryFuncCall);
+ void prepareContent();
+ void processConditionals(ASScope scope, Collection<ICompilerProblem> problems);
+ void processContents(ASScope scope, Collection<ICompilerProblem> problems);
+ }
+
/**
* Constructor.
*/
@@ -125,6 +143,10 @@ public class ForLoopNode extends FixedChildrenNode implements IForLoopNode
// Put header statements into the current block
conditionsStatementsNode.analyze(set, scope, problems);
contentsNode.analyze(set, scope, problems);
+ // add for-each kind for subsequent post-processing step at function scope level
+ if (kind == ForLoopKind.FOR_EACH) {
+ scope.addForEach(this);
+ }
}
//
@@ -171,4 +193,36 @@ public class ForLoopNode extends FixedChildrenNode implements IForLoopNode
{
return contentsNode;
}
+
+ //special case for mutating loop in post-process stage, after definitions are available
+ public Collection<ICompilerProblem> processMutation(ILoopMutation mutation, ASScope scope) {
+ assert mutation != null && mutation.getLoopTarget() == this : "Loop mutation is not aligned with target for mutation";
+ Collection<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
+ if (mutation.isValid()) {
+ IForLoopNode.ForLoopKind newKind = mutation.getKind();
+ if (kind != newKind) {
+ //handle anything related to that change
+ kind = newKind;
+ }
+ if (mutation.mutatesConditionals()) {
+ //handle anything related to that change
+ mutation.processConditionals(scope, problems);
+ }
+ if (mutation.mutatesContents()) {
+ //handle anything related to that change
+ mutation.processContents(scope, problems);
+ }
+ List<NodeBase> analyzeRequests = mutation.getAnalyzeRequests();
+ if (analyzeRequests != null) {
+ EnumSet<PostProcessStep> set = EnumSet.of(
+ PostProcessStep.POPULATE_SCOPE);
+ for (NodeBase node: analyzeRequests) {
+ node.analyze(set,scope, problems);
+ }
+ }
+
+ }
+ return problems;
+ }
}
+
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/FunctionNode.java b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/FunctionNode.java
index 54c836c..8fc51e3 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/FunctionNode.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/FunctionNode.java
@@ -832,7 +832,7 @@ public class FunctionNode extends BaseTypedDefinitionNode implements IFunctionNo
assert openT != null : "Expected '{' token.";
final String sourcePath = getSourcePath();
- assert sourcePath != null && !sourcePath.isEmpty() : "Souce path not set.";
+ assert sourcePath != null && !sourcePath.isEmpty() : "Source path not set.";
final FileNode fileNode = (FileNode)getAncestorOfType(FileNode.class);
assert fileNode != null : "FileNode not found: function " + getName();
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/TreeNode.java b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/TreeNode.java
index e9878fc..b3c0755 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/TreeNode.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/TreeNode.java
@@ -148,15 +148,14 @@ public abstract class TreeNode extends NodeBase
{
if (children instanceof List)
{
- //TODO update offsets
- CheapArray.replace(position, child, emptyNodeArray);
+ ((List) children).add(position, child);
}
else
{
ArrayList<IASNode> newArray = new ArrayList<IASNode>();
Collections.addAll(newArray, (IASNode[])children);
newArray.add(position, child);
- children = newArray.toArray();
+ children = CheapArray.toArray(newArray, emptyNodeArray);
}
child.setParent(this);
}
@@ -245,7 +244,7 @@ public abstract class TreeNode extends NodeBase
if (child != null)
{
//use safe remove call
- CheapArray.remove(child, children, emptyNodeArray);
+ children = CheapArray.remove(child, children, emptyNodeArray);
child.setParent(null);
}
}
diff --git a/compiler/src/main/java/org/apache/royale/utils/ASTUtil.java b/compiler/src/main/java/org/apache/royale/utils/ASTUtil.java
new file mode 100644
index 0000000..8cd5aaf
--- /dev/null
+++ b/compiler/src/main/java/org/apache/royale/utils/ASTUtil.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * 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.internal.tree.as.FunctionNode;
+import org.apache.royale.compiler.projects.ICompilerProject;
+
+public class ASTUtil {
+
+ public static void processFunctionNode(FunctionNode funcNode, ICompilerProject project, boolean dynamicTarget) {
+ if (funcNode.isImplicit()) {
+ //we don't care about implicit nodes (e.g. generated constructors)
+ //and they may not have all the 'real' definition data populated anyway, such as contained ASScope
+ return;
+ }
+ ArrayLikeUtil.preProcessGetterSetters(project, funcNode.getScopedNode(), null);
+ if (funcNode.getDefinition().getContainedScope().getHasForEach()) {
+ //pre-process for 'ArrayLike' for-each mutations
+ ArrayLikeUtil.preProcessForEachLoops(funcNode.getDefinition().getContainedScope(), project, dynamicTarget);
+ }
+
+ }
+
+
+}
diff --git a/compiler/src/main/java/org/apache/royale/utils/ArrayLikeUtil.java b/compiler/src/main/java/org/apache/royale/utils/ArrayLikeUtil.java
new file mode 100644
index 0000000..e32b891
--- /dev/null
+++ b/compiler/src/main/java/org/apache/royale/utils/ArrayLikeUtil.java
@@ -0,0 +1,900 @@
+/*
+ *
+ * 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.tree.as.*;
+
+import java.util.*;
+
+/**
+ * Support for processsing 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 with an actual user-coded name
+ //being extremely remote. It also assumes that the target platform will optimize its own local variable names,
+ //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 + "'";
+ }
+
+ public static void preProcessGetterSetters(ICompilerProject project, ContainerNode node, NodeBase lower ) {
+ NodeBase parent = lower != null ? lower : node;
+ int childCount = parent.getChildCount();
+ for (int i=0; i < childCount; i++) {
+ IASNode child = parent.getChild(i);
+
+ 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("[]")) { //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("[]")) {
+ 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("[]")) { //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("[]")) { //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 UnaryOperatorNodeBase) {
+ 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) {
+ boolean isCandidate = false;
+ //first check to see if the access is numeric... if it is not then we consider that it is not a candidate
+ IDefinition dynType = dynNode.getRightOperandNode().resolveType(project);
+ 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
+ * @param useDynamicAccess
+ */
+ public static void preProcessForEachLoops(ASScope searchScope,ICompilerProject project, boolean useDynamicAccess) {
+
+ ScopedBlockNode funcScopeNode = (ScopedBlockNode) searchScope.getScopeNode();
+
+ IForLoopNode[] forEachs = searchScope.getForEachs(true);
+ List<IForLoopNode> forEachList = Arrays.asList(forEachs);
+ boolean importAdded = false;
+ ArrayList<String> usedIterators = new ArrayList<String>();
+ for (IForLoopNode loopNode : forEachList) {
+ int depth = 0;
+ IASNode nodeCheck = loopNode;
+ while (nodeCheck.getParent() != null && nodeCheck.getParent() != funcScopeNode) {
+ if (nodeCheck.getParent() instanceof IForLoopNode && forEachList.indexOf(nodeCheck.getParent()) != -1) {
+ depth++;
+ }
+ nodeCheck = nodeCheck.getParent();
+ }
+ //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 {
+ 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<String> 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")); arrIter.hasNext();)
+ // {
+ // {!alreadyUsed: var} originalName{!alreadyUsed: :OriginalType} = arrIter.getNext();
+ // {originalBody Loop body}
+ // }
+
+ 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);
+
+ mutation.prepareConditionals(!alreadyUsed, specificIteratorFunc);
+ mutation.prepareContent();
+
+ Collection<ICompilerProblem> 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 HashMap<String, String> arrayLikeLookups = new HashMap<String, String>(20);
+
+ /**
+ *
+ * @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<ICompilerProblem> problems) {
+ IMetaTag arrayLikeTag = definition.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
+
+ //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) {
+ if (definition instanceof IClassDefinition) {
+ String qName = definition.getQualifiedName();
+ if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
+ return checkClass((IClassDefinition) definition, project);
+ } else if (definition instanceof IInterfaceDefinition) {
+ String qName = definition.getQualifiedName();
+ if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
+ return checkInterface((IInterfaceDefinition) definition, project);
+ }
+ }
+ return false;
+ }
+
+ private static boolean checkClass(IClassDefinition definition, ICompilerProject project){
+ String qName = definition.getQualifiedName();
+ 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<IInterfaceDefinition> 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, ICompilerProject project){
+ String qName = interfaceDefinition.getQualifiedName();
+ if (arrayLikeLookups.containsKey(qName)) return arrayLikeLookups.get(qName) != null;
+
+ boolean isArrayLike = interfaceDefinition.hasMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ARRAYLIKE);
+ String originalQName = qName;
+ if (!isArrayLike) qName = null;
+ ArrayList<String> 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<String>();
+ Iterator<IInterfaceDefinition> 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){
+ 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<NodeBase> analyzeRequests;
+ private ContainerNode mutatedConditionals = new ContainerNode();
+
+ public boolean isValid(){
+ return mutatedConditionals !=null
+ && mutatedConditionals.getChildCount() == 3
+ && iteratee != null;
+ }
+
+ @Override
+ public List<NodeBase> 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<ICompilerProblem> 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<NodeBase>();
+ }
+ for (int i=0;i<3;i++) {
+ analyzeRequests.add((NodeBase) conditionals.getChild(i));
+ }
+ }
+
+ public void processContents(ASScope scope, Collection<ICompilerProblem> problems){
+ BlockNode block = target.getContentsNode();
+ //'insert' at position 0 to prepend this to the original loop's block content
+ block.addChild(iteratee, 0);
+ EnumSet<PostProcessStep> 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<NodeBase>();
+ }
+ 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;
+ }
+
+}