This is an automated email from the ASF dual-hosted git repository. joshtynjala pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
commit 308d8867b923c0edff4bf814fa89a175123dcf37 Author: Josh Tynjala <[email protected]> AuthorDate: Tue Apr 21 14:08:25 2026 -0700 JSDynamicAccessOverride, JSForInOverride, and JSForEachOverride metadata: optionally emit custom method calls when specific syntax is encountered Intended to allow folks using Royale to emulate classes like flash.utils.Dictionary and flash.utils.ByteArray, which may support custom behaviors for dynamic access with [] square brackets, for-in loop, and for-each loop syntax. JSDynamicAccessOverride supports getMethod and setMethod properties to get and set a key, deleteMethod to delete/remove a key, and inMethod to check if a key exists. JSForInOverride supports the iteratorMethod property to create an iterator object with the target's keys. iteratorNextMethod gets the next object from the iterator. iteratorHasNextMethod or iteratorDoneMethod is used to determine if a next object exists. iteratorHasNextMethod and iteratorDoneMethod are considered optional. If they are not defined, a null result from iteratorNextMethod will be considered the end of the iterator. JSForEachOverride supports the same methods, but iterates over the values instead of the keys. If JSForEachOverride is omitted, the compiler will intelligently use JSForInOverride to loop over the keys, and then get each value dynamically at runtime (possibly using JSDynamicAccessOverride, if defined). --- RELEASE_NOTES.md | 1 + .../constants/IJSMetaAttributeConstants.java | 43 +++ .../codegen/js/jx/BinaryOperatorEmitter.java | 91 +++++- .../internal/codegen/js/jx/ClassEmitter.java | 41 +++ .../codegen/js/jx/DynamicAccessEmitter.java | 21 ++ .../internal/codegen/js/jx/ForEachEmitter.java | 337 +++++++++++++++++++++ .../internal/codegen/js/jx/ForLoopEmitter.java | 183 ++++++++++- .../codegen/js/jx/UnaryOperatorEmitter.java | 35 ++- .../problems/MissingMetaTagAttributeProblem.java | 38 +++ 9 files changed, 781 insertions(+), 9 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index efcaa3616..c1d1bc817 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -79,6 +79,7 @@ Apache Royale Compiler 1.0.0 - compiler: Fixed namespace URI when namespace is defined in package. - compiler: Fixed warning for comparison of boolean or numeric type with `null` when using `-js-default-initializers=false` compiler option. - compiler: Fixed JavaScript code generation for E4X wildcard (.*) syntax. +- compiler: Added `[JSDynamicOverride]`, `[JSForInOverride]`, and `[JSForEachOverride]` to customize the behavior of certain syntax when targeting JavaScript. Useful for emulating the full features of `Dictionary` and `ByteArray` from SWF. - debugger: Added missing isolate ID to SWF load and unload events. - debugger: Fixed debugger targeting the current JDK version instead of the intended minimum JDK version. - debugger: Fixed localized messages appearing as unprocessed tokens. diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/constants/IJSMetaAttributeConstants.java b/compiler-jx/src/main/java/org/apache/royale/compiler/constants/IJSMetaAttributeConstants.java new file mode 100644 index 000000000..0b11f585c --- /dev/null +++ b/compiler-jx/src/main/java/org/apache/royale/compiler/constants/IJSMetaAttributeConstants.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.compiler.constants; + +public interface IJSMetaAttributeConstants +{ + // [JSDynamicOverride] + static final String ATTRIBUTE_DYNAMIC_OVERRIDE = "JSDynamicOverride"; + static final String NAME_DYNAMIC_OVERRIDE_GET_METHOD = "getMethod"; + static final String NAME_DYNAMIC_OVERRIDE_SET_METHOD = "setMethod"; + static final String NAME_DYNAMIC_OVERRIDE_DELETE_METHOD = "deleteMethod"; + static final String NAME_DYNAMIC_OVERRIDE_IN_METHOD = "inMethod"; + + // [JSForInOverride] + static final String ATTRIBUTE_FOR_IN_OVERRIDE = "JSForInOverride"; + static final String NAME_FOR_IN_OVERRIDE_ITERATOR_METHOD = "iteratorMethod"; + static final String NAME_FOR_IN_OVERRIDE_ITERATOR_NEXT_METHOD = "iteratorNextMethod"; + static final String NAME_FOR_IN_OVERRIDE_ITERATOR_HAS_NEXT_METHOD = "iteratorHasNextMethod"; + static final String NAME_FOR_IN_OVERRIDE_ITERATOR_DONE_METHOD = "iteratorDoneMethod"; + + // [JSForEachOverride] + static final String ATTRIBUTE_FOR_EACH_OVERRIDE = "JSForEachOverride"; + static final String NAME_FOR_EACH_OVERRIDE_ITERATOR_METHOD = "iteratorMethod"; + static final String NAME_FOR_EACH_OVERRIDE_ITERATOR_NEXT_METHOD = "iteratorNextMethod"; + static final String NAME_FOR_EACH_OVERRIDE_ITERATOR_HAS_NEXT_METHOD = "iteratorHasNextMethod"; +} diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/BinaryOperatorEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/BinaryOperatorEmitter.java index 816e6bc6b..904909e4f 100644 --- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/BinaryOperatorEmitter.java +++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/BinaryOperatorEmitter.java @@ -21,6 +21,7 @@ package org.apache.royale.compiler.internal.codegen.js.jx; import org.apache.royale.compiler.codegen.ISubEmitter; import org.apache.royale.compiler.codegen.js.IJSEmitter; +import org.apache.royale.compiler.constants.IJSMetaAttributeConstants; import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType; import org.apache.royale.compiler.definitions.IDefinition; import org.apache.royale.compiler.definitions.IFunctionDefinition; @@ -28,6 +29,7 @@ import org.apache.royale.compiler.definitions.IFunctionDefinition.FunctionClassi import org.apache.royale.compiler.definitions.ITypeDefinition; import org.apache.royale.compiler.definitions.IVariableDefinition; import org.apache.royale.compiler.definitions.IVariableDefinition.VariableClassification; +import org.apache.royale.compiler.definitions.metadata.IMetaTag; import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter; @@ -714,6 +716,8 @@ public class BinaryOperatorEmitter extends JSSubEmitter implements } else { + boolean dynamicAssignmentOverride = false; + boolean dynamicInOverride = false; if (isAssignment && (getProject() instanceof RoyaleJSProject && ((RoyaleJSProject) getProject()).config != null && ((RoyaleJSProject) getProject()).config.getJsVectorEmulationClass() == null) && node.getLeftOperandNode() instanceof MemberAccessExpressionNode @@ -744,6 +748,44 @@ public class BinaryOperatorEmitter extends JSSubEmitter implements { getWalker().walk(node.getLeftOperandNode().getChild(1)); } + else if (isAssignment && node.getLeftOperandNode() instanceof IDynamicAccessNode) + { + IDynamicAccessNode dynamicAccessNode = (IDynamicAccessNode) node.getLeftOperandNode(); + String setMethod = getDynamicAccessSetOverride(dynamicAccessNode); + if (setMethod != null) + { + dynamicAssignmentOverride = true; + getWalker().walk(dynamicAccessNode.getLeftOperandNode()); + write(ASEmitterTokens.MEMBER_ACCESS); + write(setMethod); + write(ASEmitterTokens.PAREN_OPEN); + getWalker().walk(dynamicAccessNode.getRightOperandNode()); + write(ASEmitterTokens.COMMA); + } + else + { + // normal dynamic access + getWalker().walk(node.getLeftOperandNode()); + } + } + else if (id == ASTNodeID.Op_InID) + { + String inMethod = getDynamicAccessInOverride(node.getRightOperandNode()); + if (inMethod != null) + { + dynamicInOverride = true; + getWalker().walk(node.getRightOperandNode()); + write(ASEmitterTokens.MEMBER_ACCESS); + write(inMethod); + write(ASEmitterTokens.PAREN_OPEN); + getWalker().walk(node.getLeftOperandNode()); + } + else + { + // normal in operator + getWalker().walk(node.getLeftOperandNode()); + } + } else getWalker().walk(node.getLeftOperandNode()); startMapping(node, node.getLeftOperandNode()); boolean xmlAdd = false; @@ -751,7 +793,7 @@ public class BinaryOperatorEmitter extends JSSubEmitter implements //we need to use 'plus' method instead of '+' xmlAdd = true; } - if (id != ASTNodeID.Op_CommaID && !xmlAdd) + if (id != ASTNodeID.Op_CommaID && !xmlAdd && !dynamicAssignmentOverride && !dynamicInOverride) write(ASEmitterTokens.SPACE); // (erikdebruin) rewrite 'a &&= b' to 'a = a && b' @@ -770,26 +812,26 @@ public class BinaryOperatorEmitter extends JSSubEmitter implements write(ASEmitterTokens.SPACE); write((id == ASTNodeID.Op_LogicalAndAssignID) ? ASEmitterTokens.LOGICAL_AND : ASEmitterTokens.LOGICAL_OR); + write(ASEmitterTokens.SPACE); } - else + else if (!dynamicAssignmentOverride && !dynamicInOverride) { if (xmlAdd) { write(".plus("); } else { write(node.getOperator().getOperatorText()); + write(ASEmitterTokens.SPACE); } } - write(ASEmitterTokens.SPACE); endMapping(node); if (isAssignment) { getEmitter().emitAssignmentCoercion(node.getRightOperandNode(), node.getLeftOperandNode().resolveType(getProject())); } - else + else if (!dynamicInOverride) { - getWalker().walk(node.getRightOperandNode()); if (node.getNodeID() == ASTNodeID.Op_InID && @@ -804,7 +846,7 @@ public class BinaryOperatorEmitter extends JSSubEmitter implements write(".propertyNames()"); } } - if (xmlAdd) { + if (xmlAdd || dynamicAssignmentOverride || dynamicInOverride) { write(")"); } } @@ -812,6 +854,43 @@ public class BinaryOperatorEmitter extends JSSubEmitter implements if (ASNodeUtils.hasParenOpen(node)) write(ASEmitterTokens.PAREN_CLOSE); } + + private String getDynamicAccessSetOverride(IASNode leftSide) + { + if (!(leftSide instanceof DynamicAccessNode)) + { + return null; + } + + IDynamicAccessNode dynamicAccessNode = (IDynamicAccessNode) leftSide; + IExpressionNode dynamicLeftOperandNode = dynamicAccessNode.getLeftOperandNode(); + ITypeDefinition dynamicLeftType = dynamicLeftOperandNode.resolveType(getProject()); + if (dynamicLeftType == null) + { + return null; + } + IMetaTag dynamicOverrideMeta = dynamicLeftType.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_DYNAMIC_OVERRIDE); + if (dynamicOverrideMeta == null) + { + return null; + } + return dynamicOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_DYNAMIC_OVERRIDE_SET_METHOD); + } + + private String getDynamicAccessInOverride(IExpressionNode rightSideOfIn) + { + ITypeDefinition typeDef = rightSideOfIn.resolveType(getProject()); + if (typeDef == null) + { + return null; + } + IMetaTag dynamicOverrideMeta = typeDef.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_DYNAMIC_OVERRIDE); + if (dynamicOverrideMeta == null) + { + return null; + } + return dynamicOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_DYNAMIC_OVERRIDE_IN_METHOD); + } public static enum DatePropertiesGetters { diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ClassEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ClassEmitter.java index b3f2def6e..83a34c395 100644 --- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ClassEmitter.java +++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ClassEmitter.java @@ -25,10 +25,12 @@ import java.util.List; import org.apache.royale.compiler.asdoc.royale.ASDocComment; import org.apache.royale.compiler.codegen.ISubEmitter; import org.apache.royale.compiler.codegen.js.IJSEmitter; +import org.apache.royale.compiler.constants.IJSMetaAttributeConstants; import org.apache.royale.compiler.definitions.IClassDefinition; import org.apache.royale.compiler.definitions.IDefinition; import org.apache.royale.compiler.definitions.IFunctionDefinition; import org.apache.royale.compiler.definitions.INamespaceDefinition; +import org.apache.royale.compiler.definitions.metadata.IMetaTag; import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter; import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogEmitterTokens; @@ -40,6 +42,7 @@ import org.apache.royale.compiler.internal.scopes.ASProjectScope; import org.apache.royale.compiler.internal.scopes.FunctionScope; import org.apache.royale.compiler.internal.tree.as.IdentifierNode; import org.apache.royale.compiler.internal.tree.as.VariableNode; +import org.apache.royale.compiler.problems.MissingMetaTagAttributeProblem; import org.apache.royale.compiler.tree.ASTNodeID; import org.apache.royale.compiler.tree.as.*; import org.apache.royale.compiler.units.ICompilationUnit; @@ -57,6 +60,8 @@ public class ClassEmitter extends JSSubEmitter implements @Override public void emit(IClassNode node) { + verifyMetaOverrides(node); + boolean keepASDoc = false; boolean verbose = false; RoyaleJSProject project = (RoyaleJSProject)getEmitter().getWalker().getProject(); @@ -315,4 +320,40 @@ public class ClassEmitter extends JSSubEmitter implements endMapping(dnode); } } + + private void verifyMetaOverrides(IClassNode c) + { + for (IMetaTag forInOverrideMeta : c.getMetaTagsByName(IJSMetaAttributeConstants.ATTRIBUTE_FOR_IN_OVERRIDE)) + { + final String iteratorMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_METHOD); + final String iteratorNextMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_NEXT_METHOD); + + if (iteratorMethodName == null || iteratorMethodName.length() == 0) + { + getProject().getProblems().add(new MissingMetaTagAttributeProblem(forInOverrideMeta, IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_METHOD)); + return; + } + + if (iteratorNextMethodName == null || iteratorNextMethodName.length() == 0) + { + getProject().getProblems().add(new MissingMetaTagAttributeProblem(forInOverrideMeta, IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_NEXT_METHOD)); + } + } + for (IMetaTag forEachOverrideMeta : c.getMetaTagsByName(IJSMetaAttributeConstants.ATTRIBUTE_FOR_EACH_OVERRIDE)) + { + final String iteratorMethodName = forEachOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_METHOD); + final String iteratorNextMethodName = forEachOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_NEXT_METHOD); + + if (iteratorMethodName == null || iteratorMethodName.length() == 0) + { + getProject().getProblems().add(new MissingMetaTagAttributeProblem(forEachOverrideMeta, IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_METHOD)); + return; + } + + if (iteratorNextMethodName == null || iteratorNextMethodName.length() == 0) + { + getProject().getProblems().add(new MissingMetaTagAttributeProblem(forEachOverrideMeta, IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_NEXT_METHOD)); + } + } + } } diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/DynamicAccessEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/DynamicAccessEmitter.java index d31736107..b0392de31 100644 --- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/DynamicAccessEmitter.java +++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/DynamicAccessEmitter.java @@ -23,7 +23,9 @@ import org.apache.royale.compiler.codegen.IDocEmitter; import org.apache.royale.compiler.codegen.ISubEmitter; import org.apache.royale.compiler.codegen.js.IJSEmitter; import org.apache.royale.compiler.constants.IASLanguageConstants; +import org.apache.royale.compiler.constants.IJSMetaAttributeConstants; import org.apache.royale.compiler.definitions.ITypeDefinition; +import org.apache.royale.compiler.definitions.metadata.IMetaTag; import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter; import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleDocEmitter; @@ -89,6 +91,25 @@ public class DynamicAccessEmitter extends JSSubEmitter implements return; } } + + ITypeDefinition leftType = leftOperandNode.resolveType(getProject()); + if (leftType != null) + { + IMetaTag dynamicOverrideMeta = leftType.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_DYNAMIC_OVERRIDE); + if (dynamicOverrideMeta != null) + { + String getMethod = dynamicOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_DYNAMIC_OVERRIDE_GET_METHOD); + if (getMethod != null) + { + write(ASEmitterTokens.MEMBER_ACCESS); + write(getMethod); + write(ASEmitterTokens.PAREN_OPEN); + getWalker().walk(rightOperandNode); + write(ASEmitterTokens.PAREN_CLOSE); + return; + } + } + } startMapping(node, leftOperandNode); write(ASEmitterTokens.SQUARE_OPEN); 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 071a48140..be8d1400f 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 @@ -22,9 +22,12 @@ package org.apache.royale.compiler.internal.codegen.js.jx; import org.apache.royale.compiler.codegen.ISubEmitter; import org.apache.royale.compiler.codegen.js.IJSEmitter; import org.apache.royale.compiler.constants.IASLanguageConstants; +import org.apache.royale.compiler.constants.IJSMetaAttributeConstants; import org.apache.royale.compiler.definitions.IClassDefinition; import org.apache.royale.compiler.definitions.IDefinition; import org.apache.royale.compiler.definitions.IFunctionDefinition; +import org.apache.royale.compiler.definitions.ITypeDefinition; +import org.apache.royale.compiler.definitions.metadata.IMetaTag; import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter; import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitter; @@ -55,6 +58,24 @@ public class ForEachEmitter extends JSSubEmitter implements IExpressionNode childNode = bnode.getLeftOperandNode(); IExpressionNode rnode = bnode.getRightOperandNode(); + ITypeDefinition rtype = rnode.resolveType(getProject()); + if (rtype != null) + { + IMetaTag forEachOverrideMeta = rtype.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_FOR_EACH_OVERRIDE); + if (forEachOverrideMeta != null) + { + emitForEachOverride(node, rtype, forEachOverrideMeta); + return; + } + // it's possible to use ForInOverride for for-each loops too + IMetaTag forInOverrideMeta = rtype.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_FOR_IN_OVERRIDE); + if (forInOverrideMeta != null) + { + emitForInOverride(node, rtype, forInOverrideMeta); + return; + } + } + final String iterName = getModel().getCurrentForeachName(); getModel().incForeachLoopCount(); final String targetName = iterName + "_target"; @@ -317,4 +338,320 @@ public class ForEachEmitter extends JSSubEmitter implements } */ + private void emitForEachOverride(IForLoopNode node, ITypeDefinition rtype, IMetaTag forEachOverrideMeta) + { + IContainerNode cnode = node.getConditionalsContainerNode(); + IBinaryOperatorNode bnode = (IBinaryOperatorNode) cnode.getChild(0); + IExpressionNode childNode = bnode.getLeftOperandNode(); + IExpressionNode rnode = bnode.getRightOperandNode(); + + final String iterBaseName = getModel().getCurrentForeachName(); + getModel().incForeachLoopCount(); + final String iterTargetName = iterBaseName + "_target"; + final String iterResultName = iterBaseName + "_iterator"; + final String iterKeyName = iterBaseName + "_key"; + + final String iteratorMethodName = forEachOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_METHOD); + final String iteratorNextMethodName = forEachOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_NEXT_METHOD); + final String iteratorHasNextMethodName = forEachOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_EACH_OVERRIDE_ITERATOR_HAS_NEXT_METHOD); + final String iteratorDoneMethodName = forEachOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_DONE_METHOD); + + if (iteratorMethodName == null || iteratorNextMethodName == null) + { + return; + } + + writeToken(ASEmitterTokens.VAR); + writeToken(iterTargetName); + writeToken(ASEmitterTokens.EQUAL); + getWalker().walk(rnode); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + // if the target is null, the loop will be skipped without any + // exceptions at run-time + writeToken(ASEmitterTokens.IF); + write(ASEmitterTokens.PAREN_OPEN); + write(iterTargetName); + write(ASEmitterTokens.PAREN_CLOSE); + writeNewline(); + write(ASEmitterTokens.BLOCK_OPEN); + indentPush(); + writeNewline(); + + writeToken(ASEmitterTokens.VAR); + writeToken(iterResultName); + writeToken(ASEmitterTokens.EQUAL); + write(iterTargetName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + writeToken(ASEmitterTokens.WHILE); + write(ASEmitterTokens.PAREN_OPEN); + if (iteratorHasNextMethodName != null) + { + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorHasNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + } + else if (iteratorDoneMethodName != null) + { + write("!"); + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorDoneMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + } + else + { + write(ASEmitterTokens.TRUE); + } + write(ASEmitterTokens.PAREN_CLOSE); + writeNewline(); + write(ASEmitterTokens.BLOCK_OPEN); + indentPush(); + writeNewline(); + + if (iteratorHasNextMethodName == null && iteratorDoneMethodName == null) + { + writeToken(ASEmitterTokens.VAR); + writeToken(iterKeyName); + writeToken(ASEmitterTokens.EQUAL); + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + writeToken(ASEmitterTokens.IF); + write(ASEmitterTokens.PAREN_OPEN); + writeToken(iterKeyName); + writeToken("=="); + write(ASEmitterTokens.UNDEFINED); + writeToken(ASEmitterTokens.PAREN_CLOSE); + write("break"); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + + if (childNode instanceof IVariableExpressionNode) + { + startMapping(childNode); + write(ASEmitterTokens.VAR); + write(ASEmitterTokens.SPACE); + 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 { //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); + } + + if (iteratorHasNextMethodName == null && iteratorDoneMethodName == null) + { + write(iterKeyName); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + else + { + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + + getWalker().walk(node.getStatementContentsNode()); + + write(ASEmitterTokens.BLOCK_CLOSE); + indentPop(); + writeNewline(); + + write(ASEmitterTokens.BLOCK_CLOSE); + indentPop(); + writeNewline(); + } + + private void emitForInOverride(IForLoopNode node, ITypeDefinition rtype, IMetaTag forInOverrideMeta) + { + IContainerNode cnode = node.getConditionalsContainerNode(); + IBinaryOperatorNode bnode = (IBinaryOperatorNode) cnode.getChild(0); + IExpressionNode childNode = bnode.getLeftOperandNode(); + IExpressionNode rnode = bnode.getRightOperandNode(); + + IMetaTag dynamicOverrideMeta = rtype.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_DYNAMIC_OVERRIDE); + String getMethod = null; + if (dynamicOverrideMeta != null) + { + getMethod = dynamicOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_DYNAMIC_OVERRIDE_GET_METHOD); + } + + final String iterBaseName = getModel().getCurrentForeachName(); + getModel().incForeachLoopCount(); + final String iterTargetName = iterBaseName + "_target"; + final String iterResultName = iterBaseName + "_iterator"; + final String iterKeyName = iterBaseName + "_key"; + + final String iteratorMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_METHOD); + final String iteratorNextMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_NEXT_METHOD); + final String iteratorHasNextMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_HAS_NEXT_METHOD); + final String iteratorDoneMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_DONE_METHOD); + + if (iteratorMethodName == null || iteratorNextMethodName == null) + { + return; + } + + writeToken(ASEmitterTokens.VAR); + writeToken(iterTargetName); + writeToken(ASEmitterTokens.EQUAL); + getWalker().walk(rnode); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + // if the target is null, the loop will be skipped without any + // exceptions at run-time + writeToken(ASEmitterTokens.IF); + write(ASEmitterTokens.PAREN_OPEN); + write(iterTargetName); + write(ASEmitterTokens.PAREN_CLOSE); + writeNewline(); + write(ASEmitterTokens.BLOCK_OPEN); + indentPush(); + writeNewline(); + + writeToken(ASEmitterTokens.VAR); + writeToken(iterResultName); + writeToken(ASEmitterTokens.EQUAL); + write(iterTargetName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + writeToken(ASEmitterTokens.WHILE); + write(ASEmitterTokens.PAREN_OPEN); + if (iteratorHasNextMethodName != null) + { + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorHasNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + } + else if (iteratorDoneMethodName != null) + { + write("!"); + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorDoneMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + } + else + { + write(ASEmitterTokens.TRUE); + } + write(ASEmitterTokens.PAREN_CLOSE); + writeNewline(); + write(ASEmitterTokens.BLOCK_OPEN); + indentPush(); + writeNewline(); + + writeToken(ASEmitterTokens.VAR); + writeToken(iterKeyName); + writeToken(ASEmitterTokens.EQUAL); + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + if (iteratorHasNextMethodName == null && iteratorDoneMethodName == null) + { + writeToken(ASEmitterTokens.IF); + write(ASEmitterTokens.PAREN_OPEN); + writeToken(iterKeyName); + writeToken("=="); + write(ASEmitterTokens.UNDEFINED); + writeToken(ASEmitterTokens.PAREN_CLOSE); + write("break"); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + + if (childNode instanceof IVariableExpressionNode) + { + startMapping(childNode); + write(ASEmitterTokens.VAR); + write(ASEmitterTokens.SPACE); + 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 { //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); + } + + write(iterTargetName); + if (getMethod != null) + { + write(ASEmitterTokens.MEMBER_ACCESS); + write(getMethod); + write(ASEmitterTokens.PAREN_OPEN); + write(iterKeyName); + write(ASEmitterTokens.PAREN_CLOSE); + } + else + { + write(ASEmitterTokens.SQUARE_OPEN); + write(iterKeyName); + write(ASEmitterTokens.SQUARE_CLOSE); + } + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + getWalker().walk(node.getStatementContentsNode()); + + write(ASEmitterTokens.BLOCK_CLOSE); + indentPop(); + writeNewline(); + + write(ASEmitterTokens.BLOCK_CLOSE); + indentPop(); + writeNewline(); + } + } diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForLoopEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForLoopEmitter.java index 21ee8d98c..0a4ad168d 100644 --- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForLoopEmitter.java +++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/ForLoopEmitter.java @@ -20,13 +20,20 @@ package org.apache.royale.compiler.internal.codegen.js.jx; import org.apache.royale.compiler.codegen.ISubEmitter; import org.apache.royale.compiler.codegen.js.IJSEmitter; +import org.apache.royale.compiler.constants.IJSMetaAttributeConstants; +import org.apache.royale.compiler.definitions.ITypeDefinition; +import org.apache.royale.compiler.definitions.metadata.IMetaTag; import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter; import org.apache.royale.compiler.internal.codegen.js.utils.EmitterUtils; import org.apache.royale.compiler.tree.ASTNodeID; import org.apache.royale.compiler.tree.as.IASNode; +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.IVariableExpressionNode; +import org.apache.royale.compiler.tree.as.IVariableNode; public class ForLoopEmitter extends JSSubEmitter implements ISubEmitter<IForLoopNode> @@ -40,14 +47,32 @@ public class ForLoopEmitter extends JSSubEmitter implements public void emit(IForLoopNode node) { IContainerNode statementContentsNode = (IContainerNode) node.getStatementContentsNode(); + IContainerNode cnode = node.getConditionalsContainerNode(); + final IASNode node0 = cnode.getChild(0); + + if (node0.getNodeID() == ASTNodeID.Op_InID) + { + IBinaryOperatorNode inNode = (IBinaryOperatorNode) node0; + IExpressionNode rnode = inNode.getRightOperandNode(); + + + ITypeDefinition rtype = rnode.resolveType(getProject()); + if (rtype != null) + { + IMetaTag forInOverrideMeta = rtype.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_FOR_IN_OVERRIDE); + if (forInOverrideMeta != null) + { + emitForInOverride(node, rtype, forInOverrideMeta); + return; + } + } + } startMapping(node); writeToken(ASEmitterTokens.FOR); write(ASEmitterTokens.PAREN_OPEN); endMapping(node); - IContainerNode cnode = node.getConditionalsContainerNode(); - final IASNode node0 = cnode.getChild(0); if (node0.getNodeID() == ASTNodeID.Op_InID) { //for(in) @@ -125,4 +150,158 @@ public class ForLoopEmitter extends JSSubEmitter implements getWalker().walk(node2); } } + + private void emitForInOverride(IForLoopNode node, ITypeDefinition rtype, IMetaTag forInOverrideMeta) + { + IContainerNode cnode = node.getConditionalsContainerNode(); + IBinaryOperatorNode bnode = (IBinaryOperatorNode) cnode.getChild(0); + IExpressionNode childNode = bnode.getLeftOperandNode(); + IExpressionNode rnode = bnode.getRightOperandNode(); + + final String iterBaseName = getModel().getCurrentForeachName(); + getModel().incForeachLoopCount(); + final String iterTargetName = iterBaseName + "_target"; + final String iterResultName = iterBaseName + "_iterator"; + final String iterKeyName = iterBaseName + "_key"; + + final String iteratorMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_METHOD); + final String iteratorNextMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_NEXT_METHOD); + final String iteratorHasNextMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_HAS_NEXT_METHOD); + final String iteratorDoneMethodName = forInOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_FOR_IN_OVERRIDE_ITERATOR_DONE_METHOD); + + if (iteratorMethodName == null || iteratorNextMethodName == null) + { + return; + } + + writeToken(ASEmitterTokens.VAR); + writeToken(iterTargetName); + writeToken(ASEmitterTokens.EQUAL); + getWalker().walk(rnode); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + // if the target is null, the loop will be skipped without any + // exceptions at run-time + writeToken(ASEmitterTokens.IF); + write(ASEmitterTokens.PAREN_OPEN); + write(iterTargetName); + write(ASEmitterTokens.PAREN_CLOSE); + writeNewline(); + write(ASEmitterTokens.BLOCK_OPEN); + indentPush(); + writeNewline(); + + writeToken(ASEmitterTokens.VAR); + writeToken(iterResultName); + writeToken(ASEmitterTokens.EQUAL); + write(iterTargetName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + writeToken(ASEmitterTokens.WHILE); + write(ASEmitterTokens.PAREN_OPEN); + if (iteratorHasNextMethodName != null) + { + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorHasNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + } + else if (iteratorDoneMethodName != null) + { + write("!"); + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorDoneMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + } + else + { + write(ASEmitterTokens.TRUE); + } + write(ASEmitterTokens.PAREN_CLOSE); + writeNewline(); + write(ASEmitterTokens.BLOCK_OPEN); + indentPush(); + writeNewline(); + + if (iteratorHasNextMethodName == null && iteratorDoneMethodName == null) + { + writeToken(ASEmitterTokens.VAR); + writeToken(iterKeyName); + writeToken(ASEmitterTokens.EQUAL); + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + + writeToken(ASEmitterTokens.IF); + write(ASEmitterTokens.PAREN_OPEN); + writeToken(iterKeyName); + writeToken("=="); + write(ASEmitterTokens.UNDEFINED); + writeToken(ASEmitterTokens.PAREN_CLOSE); + write("break"); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + + if (childNode instanceof IVariableExpressionNode) + { + startMapping(childNode); + write(ASEmitterTokens.VAR); + write(ASEmitterTokens.SPACE); + 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 { //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); + } + + if (iteratorHasNextMethodName == null && iteratorDoneMethodName == null) + { + write(iterKeyName); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + else + { + write(iterResultName); + write(ASEmitterTokens.MEMBER_ACCESS); + write(iteratorNextMethodName); + write(ASEmitterTokens.PAREN_OPEN); + write(ASEmitterTokens.PAREN_CLOSE); + write(ASEmitterTokens.SEMICOLON); + writeNewline(); + } + + getWalker().walk(node.getStatementContentsNode()); + + write(ASEmitterTokens.BLOCK_CLOSE); + indentPop(); + writeNewline(); + + write(ASEmitterTokens.BLOCK_CLOSE); + indentPop(); + writeNewline(); + } } diff --git a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/UnaryOperatorEmitter.java b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/UnaryOperatorEmitter.java index 2bc5a79a1..72475713c 100644 --- a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/UnaryOperatorEmitter.java +++ b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/js/jx/UnaryOperatorEmitter.java @@ -21,6 +21,9 @@ package org.apache.royale.compiler.internal.codegen.js.jx; import org.apache.royale.compiler.codegen.ISubEmitter; import org.apache.royale.compiler.codegen.js.IJSEmitter; +import org.apache.royale.compiler.constants.IJSMetaAttributeConstants; +import org.apache.royale.compiler.definitions.ITypeDefinition; +import org.apache.royale.compiler.definitions.metadata.IMetaTag; import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens; import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter; import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitter; @@ -29,6 +32,7 @@ import org.apache.royale.compiler.internal.definitions.AppliedVectorDefinition; import org.apache.royale.compiler.internal.projects.RoyaleJSProject; import org.apache.royale.compiler.internal.tree.as.*; import org.apache.royale.compiler.tree.ASTNodeID; +import org.apache.royale.compiler.tree.as.IDynamicAccessNode; import org.apache.royale.compiler.tree.as.IExpressionNode; import org.apache.royale.compiler.tree.as.ILiteralNode; import org.apache.royale.compiler.tree.as.IUnaryOperatorNode; @@ -135,10 +139,39 @@ public class UnaryOperatorEmitter extends JSSubEmitter implements protected void emitDeleteOperator(IUnaryOperatorNode node) { + IExpressionNode operandNode = node.getOperandNode(); + if (operandNode instanceof IDynamicAccessNode) + { + IDynamicAccessNode dynamicAccessNode = (IDynamicAccessNode) operandNode; + IExpressionNode leftOperandNode = dynamicAccessNode.getLeftOperandNode(); + ITypeDefinition leftType = leftOperandNode.resolveType(getProject()); + if (leftType != null) + { + IMetaTag dynamicOverrideMeta = leftType.getMetaTagByName(IJSMetaAttributeConstants.ATTRIBUTE_DYNAMIC_OVERRIDE); + if (dynamicOverrideMeta != null) + { + String deleteMethod = dynamicOverrideMeta.getAttributeValue(IJSMetaAttributeConstants.NAME_DYNAMIC_OVERRIDE_DELETE_METHOD); + if (deleteMethod != null) + { + getWalker().walk(dynamicAccessNode.getLeftOperandNode()); + startMapping(node); + write(ASEmitterTokens.MEMBER_ACCESS); + write(deleteMethod); + write(ASEmitterTokens.PAREN_OPEN); + endMapping(node); + getWalker().walk(dynamicAccessNode.getRightOperandNode()); + startMapping(node); + write(ASEmitterTokens.PAREN_CLOSE); + endMapping(node); + return; + } + } + } + } startMapping(node); writeToken(node.getOperator().getOperatorText()); endMapping(node); - getWalker().walk(node.getOperandNode()); + getWalker().walk(operandNode); } protected void emitVoidOperator(IUnaryOperatorNode node) diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/MissingMetaTagAttributeProblem.java b/compiler/src/main/java/org/apache/royale/compiler/problems/MissingMetaTagAttributeProblem.java new file mode 100644 index 000000000..5c80fc7b6 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/MissingMetaTagAttributeProblem.java @@ -0,0 +1,38 @@ +/* + * + * 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.definitions.metadata.IMetaTag; + +public final class MissingMetaTagAttributeProblem extends CompilerProblem +{ + public static final String DESCRIPTION = + "${metaTagName} metadata requires '${attribute}' attribute"; + + public MissingMetaTagAttributeProblem(IMetaTag site, String attribute) + { + super(site); + metaTagName = site.getTagName(); + this.attribute = attribute; + } + + public String metaTagName; + public String attribute; +}
