blob: 46fc13a05efdaec8ac4f469c4ddcb71add145ca9 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.compiler.internal.codegen.js.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.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
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;
import org.apache.royale.compiler.internal.codegen.js.utils.EmitterUtils;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.FunctionCallNode;
import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
import org.apache.royale.compiler.internal.tree.as.LabeledStatementNode;
import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode;
import org.apache.royale.compiler.scopes.IDefinitionSet;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.*;
public class ForEachEmitter extends JSSubEmitter implements
ISubEmitter<IForLoopNode>
{
public ForEachEmitter(IJSEmitter emitter)
{
super(emitter);
}
@Override
public void emit(IForLoopNode node)
{
IContainerNode cnode = node.getConditionalsContainerNode();
IBinaryOperatorNode bnode = (IBinaryOperatorNode) cnode.getChild(0);
IExpressionNode childNode = bnode.getLeftOperandNode();
IExpressionNode rnode = bnode.getRightOperandNode();
final String iterName = getModel().getCurrentForeachName();
getModel().incForeachLoopCount();
final String targetName = iterName + "_target";
startMapping(rnode);
write(ASEmitterTokens.VAR);
write(ASEmitterTokens.SPACE);
write(targetName);
write(ASEmitterTokens.SPACE);
write(ASEmitterTokens.EQUAL);
write(ASEmitterTokens.SPACE);
endMapping(rnode);
IASNode obj = bnode.getChild(1);
getWalker().walk(obj);
startMapping(rnode);
write(ASEmitterTokens.SEMICOLON);
endMapping(rnode);
writeNewline();
if (node.getParent().getNodeID() == ASTNodeID.BlockID &&
node.getParent().getParent().getNodeID() == ASTNodeID.LabledStatementID)
{
// emit label here
LabeledStatementNode labelNode = (LabeledStatementNode)node.getParent().getParent();
writeToken(labelNode.getLabel());
writeToken(ASEmitterTokens.COLON);
}
startMapping(node);
write(ASEmitterTokens.FOR);
write(ASEmitterTokens.SPACE);
write(ASEmitterTokens.PAREN_OPEN);
endMapping(node);
startMapping(rnode);
write(ASEmitterTokens.VAR);
write(ASEmitterTokens.SPACE);
write(iterName);
endMapping(rnode);
startMapping(bnode, childNode);
write(ASEmitterTokens.SPACE);
write(ASEmitterTokens.IN);
write(ASEmitterTokens.SPACE);
endMapping(bnode);
startMapping(rnode);
write(targetName);
boolean isXML = false;
boolean isProxy = false;
if (obj.getNodeID() == ASTNodeID.IdentifierID)
{
if (((JSRoyaleEmitter)getEmitter()).isXMLish((IdentifierNode)obj))
{
write(".elementNames()");
isXML = true;
}
if (((JSRoyaleEmitter)getEmitter()).isProxy((IdentifierNode)obj))
{
write(".propertyNames()");
isProxy = true;
}
}
else if (obj.getNodeID() == ASTNodeID.Op_DescendantsID)
{
//it should always be XMLList... but check anyway
if (((JSRoyaleEmitter)getEmitter()).isXMLList((IMemberAccessExpressionNode)obj))
{
write(".elementNames()");
isXML = true;
}
}
else if (obj.getNodeID() == ASTNodeID.MemberAccessExpressionID)
{
if (((JSRoyaleEmitter)getEmitter()).isXMLList((IMemberAccessExpressionNode)obj))
{
write(".elementNames()");
isXML = true;
}
if (((JSRoyaleEmitter)getEmitter()).isProxy((IMemberAccessExpressionNode)obj))
{
write(".propertyNames()");
isProxy = true;
}
}
else if (obj.getNodeID() == ASTNodeID.Op_AsID)
{
IASNode asChild = obj.getChild(1);
if (asChild.getNodeID() == ASTNodeID.IdentifierID)
{
String asName = ((IdentifierNode)asChild).getName();
if (asName.equals(IASLanguageConstants.XML) || asName.equals(IASLanguageConstants.XMLList))
{
write(".elementNames()");
isXML = true;
}
}
}
else if (obj.getNodeID() == ASTNodeID.FunctionCallID)
{
FunctionCallNode func = (FunctionCallNode)obj;
IExpressionNode funcName = func.getNameNode();
if (funcName.getNodeID() == ASTNodeID.IdentifierID)
{
String asName = ((IdentifierNode)funcName).getName();
if (asName.equals(IASLanguageConstants.XML) || asName.equals(IASLanguageConstants.XMLList))
{
write(".elementNames()");
isXML = true;
}
} else if (funcName instanceof IMemberAccessExpressionNode) {
IFunctionDefinition funcDef = (IFunctionDefinition) ((IMemberAccessExpressionNode) funcName).getRightOperandNode().resolve(getProject());
if (funcDef == null) {
//we need to check the LHS for XMLishness, and then resolve the method name against the determined XMLish definition (XML or XMLList),
// and then check its return type once we find the public FunctionDefinition for the method name
// (because although it is a member of something XMLish, it may not return something that is also XMLish, such as a QName, a String, a uint, or a Namespace etc)
IDefinitionSet matchingDefinitions = null;
if (EmitterUtils.isLeftNodeXML(((IMemberAccessExpressionNode) funcName).getLeftOperandNode(), getProject())) {
if (((IMemberAccessExpressionNode) funcName).getRightOperandNode().getNodeID() == ASTNodeID.IdentifierID) {
matchingDefinitions = getProject().getBuiltinType(IASLanguageConstants.BuiltinType.XML).getContainedScope().getLocalDefinitionSetByName(((IIdentifierNode)((IMemberAccessExpressionNode) funcName).getRightOperandNode()).getName());
}
} else if (EmitterUtils.isLeftNodeXMLList(((IMemberAccessExpressionNode) funcName).getLeftOperandNode(), getProject())) {
if (((IMemberAccessExpressionNode) funcName).getRightOperandNode().getNodeID() == ASTNodeID.IdentifierID) {
matchingDefinitions = getProject().getBuiltinType(IASLanguageConstants.BuiltinType.XMLLIST).getContainedScope().getLocalDefinitionSetByName(((IIdentifierNode)((IMemberAccessExpressionNode) funcName).getRightOperandNode()).getName());
}
}
if (matchingDefinitions != null) {
for (int i = 0; i< matchingDefinitions.getSize(); i++) {
IDefinition functionDefinition = matchingDefinitions.getDefinition(i);
if (functionDefinition instanceof IFunctionDefinition) {
if (functionDefinition.isPublic()) {
isXML = SemanticUtils.isXMLish((((IFunctionDefinition) functionDefinition).resolveReturnType(getProject())), getProject());
break;
}
}
}
}
//@todo should we emit a warning here if wasXMLish (from either of the first 2 checks) && !isXML (from the matchingDefinitions check)?
// results will not be consistent in this case.
// e.g. looping over a QName or Namespace instance
// It is probably rare and ill-advised, but it definitely won't work well in javascript currently for those classes, for example.
} else {
isXML = SemanticUtils.isXMLish(funcDef.resolveReturnType(getProject()), getProject());
}
if (isXML) {
write(".elementNames()");
}
} //@todo what about dynamic access node for function call? e.g. myXML[string_Value_Here]() ... not so easy really, would likely need a runtime helper/wrapper.
}
endMapping(rnode);
startMapping(node, cnode);
writeToken(ASEmitterTokens.PAREN_CLOSE);
endMapping(node);
writeNewline();
write(ASEmitterTokens.BLOCK_OPEN);
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);
}
startMapping(rnode);
write(targetName);
if (isXML)
{
write("[");
write(iterName);
write("]");
}
else if (isProxy)
{
write(".getProperty(");
write(iterName);
write(")");
}
else
{
write(ASEmitterTokens.SQUARE_OPEN);
write(iterName);
write(ASEmitterTokens.SQUARE_CLOSE);
}
write(ASEmitterTokens.SEMICOLON);
endMapping(rnode);
writeNewline();
getWalker().walk(node.getStatementContentsNode());
write(ASEmitterTokens.BLOCK_CLOSE);
writeNewline();
}
}