| /* |
| * |
| * 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.tree.as; |
| |
| import static com.google.common.base.Predicates.and; |
| import static com.google.common.collect.Collections2.filter; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import org.apache.royale.compiler.common.ASImportTarget; |
| import org.apache.royale.compiler.common.IImportTarget; |
| import org.apache.royale.compiler.config.CompilerDiagnosticsConstants; |
| 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.IDefinition; |
| import org.apache.royale.compiler.definitions.INamespaceDefinition; |
| import org.apache.royale.compiler.definitions.IFunctionDefinition.FunctionClassification; |
| import org.apache.royale.compiler.definitions.IParameterDefinition; |
| import org.apache.royale.compiler.definitions.references.IReference; |
| import org.apache.royale.compiler.definitions.references.ReferenceFactory; |
| import org.apache.royale.compiler.internal.definitions.FunctionDefinition; |
| import org.apache.royale.compiler.internal.definitions.NamespaceDefinition; |
| import org.apache.royale.compiler.internal.definitions.ParameterDefinition; |
| import org.apache.royale.compiler.internal.definitions.VariableDefinition; |
| import org.apache.royale.compiler.internal.parsing.as.ASParser; |
| import org.apache.royale.compiler.internal.parsing.as.ASToken; |
| import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes; |
| import org.apache.royale.compiler.internal.parsing.as.ConfigProcessor; |
| import org.apache.royale.compiler.internal.scopes.ASFileScope; |
| import org.apache.royale.compiler.internal.scopes.ASScope; |
| import org.apache.royale.compiler.internal.scopes.ClosureScope; |
| import org.apache.royale.compiler.internal.scopes.FunctionScope; |
| import org.apache.royale.compiler.internal.semantics.PostProcessStep; |
| import org.apache.royale.compiler.internal.tree.as.parts.FunctionContentsPart; |
| import org.apache.royale.compiler.internal.tree.as.parts.IFunctionContentsPart; |
| import org.apache.royale.compiler.parsing.IASToken; |
| import org.apache.royale.compiler.problems.CanNotInsertSemicolonProblem; |
| import org.apache.royale.compiler.problems.ICompilerProblem; |
| import org.apache.royale.compiler.problems.InternalCompilerProblem2; |
| import org.apache.royale.compiler.scopes.IASScope; |
| 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.IContainerNode; |
| import org.apache.royale.compiler.tree.as.IDefinitionNode; |
| import org.apache.royale.compiler.tree.as.IExpressionNode; |
| import org.apache.royale.compiler.tree.as.IFunctionNode; |
| import org.apache.royale.compiler.tree.as.INamespaceDecorationNode; |
| import org.apache.royale.compiler.tree.as.IParameterNode; |
| import org.apache.royale.compiler.tree.as.IScopedNode; |
| import org.apache.royale.compiler.tree.as.ITypeNode; |
| import org.apache.royale.compiler.tree.as.IVariableNode; |
| import org.apache.royale.compiler.tree.metadata.IMetaTagNode; |
| import org.apache.royale.compiler.workspaces.IWorkspace; |
| |
| import com.google.common.base.Predicate; |
| |
| /** |
| * ActionScript parse tree node representing a function definition |
| */ |
| public class FunctionNode extends BaseTypedDefinitionNode implements IFunctionNode |
| { |
| /** |
| * Constructor. |
| * <p> |
| * Creates a {@code FunctionNode} from the "new" keyword token and the |
| * function name node. |
| * |
| * @param functionKeyword function keyword |
| * @param nameNode node containing the name of this function |
| */ |
| public FunctionNode(IASToken functionKeyword, IdentifierNode nameNode) |
| { |
| init(nameNode); |
| |
| if (functionKeyword != null) |
| contentsPart.setKeywordNode(new KeywordNode(functionKeyword)); |
| } |
| |
| /** |
| * Constructor. |
| * <p> |
| * Creates a new FunctionNode with a custom part for this functions |
| * contents. |
| * |
| * @param node the name of the node |
| * @param part the {@link IFunctionContentsPart} |
| */ |
| public FunctionNode(IdentifierNode node, IFunctionContentsPart part) |
| { |
| super.init(node); |
| contentsPart = part; |
| } |
| |
| /** |
| * Contents of the function, including args, etc |
| */ |
| protected IFunctionContentsPart contentsPart; |
| |
| /** |
| * Does this method need a Definition added for "arguments". This will be |
| * set to true during scope building if we encounter an IdentifierNode that |
| * refers to "arguments". |
| */ |
| boolean needsArguments = false; |
| |
| /** |
| * The configuration processor used to re-parse the function body. |
| */ |
| private ConfigProcessor configProcessor = null; |
| |
| /** |
| * The open curly token of the function body. |
| */ |
| private ASToken openT = null; |
| |
| /** |
| * True if the function body is a deferred node. |
| */ |
| private boolean isBodyDeferred = false; |
| |
| /** |
| * Lock used when parsing the function body |
| */ |
| private ReentrantLock deferredBodyParsingLock = new ReentrantLock(); |
| |
| /** |
| * A count of the number of calls to parse the function body. The body |
| * won't actually be thrown away until the count reaches zero again. |
| */ |
| private int deferredBodyParsingReferenceCount = 0; |
| |
| /** |
| * Cached function body text. If it's empty, the function body has to be |
| * reloaded from the file using a seek-able reader, which might be slow for |
| * large file. |
| */ |
| private String functionBodyText; |
| |
| |
| /** |
| * Save the problems until later if we were parsed from somewhere we don't have a problems collection |
| */ |
| private Collection<ICompilerProblem> parseProblems; |
| |
| /** |
| * Indicates whether we've called rememberLocalFunction() on the parent |
| * function yet (if a parent function even exists in the first place) -JT |
| */ |
| private boolean isRemembered = false; |
| |
| // |
| // NodeBase overrides |
| // |
| |
| @Override |
| public ASTNodeID getNodeID() |
| { |
| return ASTNodeID.FunctionID; |
| } |
| |
| @Override |
| public int getSpanningStart() |
| { |
| return getNodeStartForTooling(); |
| } |
| |
| @Override |
| protected void setChildren(boolean fillInOffsets) |
| { |
| addDecorationChildren(fillInOffsets); |
| addChildInOrder(contentsPart.getFunctionKeywordNode(), fillInOffsets); |
| addChildInOrder(nameNode, fillInOffsets); |
| addChildInOrder(contentsPart.getParametersNode(), fillInOffsets); |
| addChildInOrder(typeNode, fillInOffsets); |
| addChildInOrder(contentsPart.getContents(), fillInOffsets); |
| } |
| |
| @Override |
| public void normalize(boolean fillInOffsets) |
| { |
| super.normalize(fillInOffsets); |
| contentsPart.optimize(); |
| } |
| |
| @Override |
| protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems) |
| { |
| if (!isRemembered) |
| { |
| //previously, we remembered local functions only during |
| //POPULATE_SCOPE. however, in MXML, functions get created multiple |
| //times, and the second time around, analyze() is NOT called with |
| //POPULATE_SCOPE. this causes our function to be forgotten. |
| //better to check if we've remembered or not no matter which steps |
| //were passed in. -JT |
| final IFunctionNode parentFunctionNode = (IFunctionNode)getAncestorOfType(IFunctionNode.class); |
| if (parentFunctionNode != null) |
| { |
| isRemembered = true; |
| parentFunctionNode.rememberLocalFunction(this); |
| } |
| } |
| |
| if (set.contains(PostProcessStep.POPULATE_SCOPE)) |
| { |
| FunctionDefinition definition = buildDefinition(); |
| setDefinition(definition); |
| |
| // if the parent is an anonymous function, then don't add the function definition to the scope |
| // we have already added the anonymous function to the scope and this function definition does not |
| // belong to the scope |
| if (this.getParent() instanceof FunctionObjectNode) |
| { |
| String funcName = definition.getBaseName(); |
| if (funcName.length() == 0) |
| scope.setAsContainingScopeOfAnonymousFunction(definition); |
| else |
| { |
| // If the parent is an "anonymous" function with a name, then |
| // the name will have to be visibly within the function body, so it can |
| // call itself recursively. So make a special closure scope |
| // Add a closure scope below the containing scope |
| ASScope closureScope = new ClosureScope(scope); |
| scope = closureScope; // now build the function scope below this.. |
| scope.addDefinition(definition); // This sets the containing scope of the def |
| } |
| } |
| else |
| scope.addDefinition(definition); // This sets the containing scope of the def |
| |
| ScopedBlockNode contents = contentsPart.getContents(); |
| if (contents != null) |
| { |
| ASScope localScope = new FunctionScope(scope, contents); |
| definition.setContainedScope(localScope); |
| scope = localScope; |
| } |
| } |
| |
| if (set.contains(PostProcessStep.RECONNECT_DEFINITIONS)) |
| { |
| reconnectDef(scope); |
| final FunctionDefinition functionDef = this.getDefinition(); |
| if (functionDef != null) |
| { |
| scope = functionDef.getContainedScope(); |
| ScopedBlockNode contents = contentsPart.getContents(); |
| // scope can be null for generated binding wrappers of |
| // getters and setters |
| if (contents != null && scope != null) |
| { |
| contents.reconnectScope(scope); |
| } |
| } |
| } |
| |
| // Recurse on the function parameters. |
| ContainerNode parameters = contentsPart.getParametersNode(); |
| if (parameters != null) |
| { |
| parameters.analyze(set, scope, problems); |
| if (set.contains(PostProcessStep.POPULATE_SCOPE)) |
| { |
| // Set the parameters. |
| IParameterNode[] argumentNodes = getParameterNodes(); |
| int n = argumentNodes.length; |
| ParameterDefinition[] arguments = new ParameterDefinition[n]; |
| for (int i = 0; i < n; i++) |
| { |
| if (argumentNodes[i] instanceof ParameterNode) |
| arguments[i] = (ParameterDefinition)((ParameterNode)argumentNodes[i]).getDefinition(); |
| } |
| |
| ((FunctionDefinition)this.definition).setParameters(arguments); |
| } |
| |
| } |
| // Recurse on the function block. |
| BlockNode contents = contentsPart.getContents(); |
| if (contents != null) |
| contents.analyze(set, scope, problems); |
| |
| if (set.contains(PostProcessStep.POPULATE_SCOPE)) |
| tryAddDefaultArgument(); |
| } |
| |
| /* |
| * For debugging only. |
| * Builds a string such as <code>"doSomething(int, String):void"</code> |
| * from the signature of the function being defined. |
| */ |
| @Override |
| protected boolean buildInnerString(StringBuilder sb) |
| { |
| sb.append(getName()); |
| |
| sb.append('('); |
| IVariableNode[] args = getParameterNodes(); |
| for (int i = 0; i < args.length; i++) |
| { |
| IVariableNode arg = args[i]; |
| sb.append(arg.getVariableType()); |
| if (i < args.length - 1) |
| sb.append(", "); |
| } |
| sb.append(')'); |
| |
| if (getReturnType().length() > 0) |
| sb.append(":" + getReturnType()); |
| |
| return true; |
| } |
| |
| // |
| // TreeNode overrides |
| // |
| |
| @Override |
| protected int getInitialChildCount() |
| { |
| return 4; |
| } |
| |
| // |
| // BaseDefinitionNode overrides overrides |
| // |
| |
| @Override |
| protected void init(ExpressionNodeBase idNode) |
| { |
| super.init(idNode); |
| |
| contentsPart = createContentsPart(); |
| } |
| |
| @Override |
| public INamespaceDecorationNode getNamespaceNode() |
| { |
| INamespaceDecorationNode namespaceNode = super.getNamespaceNode(); |
| |
| if (isConstructor()) |
| { |
| if (namespaceNode != null |
| && (namespaceNode.getName().equals(INamespaceConstants.public_) || namespaceNode.getName().equals(INamespaceConstants.private_))) |
| { |
| // if the existing node is already public or private, return it |
| return namespaceNode; |
| } |
| IASNode parentNode = getParent(); |
| if (parentNode instanceof IDefinitionNode) |
| { |
| IDefinitionNode defNode = (IDefinitionNode) parentNode; |
| IMetaTagNode[] metaTagNodes = defNode.getMetaTags().getTagsByName(IMetaAttributeConstants.ATTRIBUTE_PRIVATE_CONSTRUCTOR); |
| if (metaTagNodes != null && metaTagNodes.length > 0) |
| { |
| // if the parent class has [RoyalePrivateConstructor] |
| // metadata, the constructor should be considered private |
| // and we should generate a fake namespace node |
| NamespaceIdentifierNode priv = new NamespaceIdentifierNode(INamespaceConstants.private_); |
| priv.span(-1, -1, -1, -1, -1, -1); |
| priv.setDecorationTarget(this); |
| return priv; |
| } |
| } |
| // if there is no namespace node, the namespace defaults to public |
| // and we'll generate a fake node |
| NamespaceIdentifierNode pub = new NamespaceIdentifierNode(INamespaceConstants.public_); |
| pub.span(-1, -1, -1, -1, -1, -1); |
| pub.setDecorationTarget(this); |
| return pub; |
| } |
| |
| return namespaceNode; |
| } |
| |
| @Override |
| public String getNamespace() |
| { |
| INamespaceDecorationNode ns = getNamespaceNode(); |
| if (ns != null) |
| { |
| String nameString = ns.getName(); |
| // If public or private, just return it. |
| if (nameString.equals(INamespaceConstants.public_) || nameString.equals(INamespaceConstants.private_)) |
| return nameString; |
| |
| // Otherwise, check to see if we are a constructor and always return |
| // public |
| if (isConstructor()) |
| return INamespaceConstants.public_; |
| |
| // Just return the value. |
| return nameString; |
| } |
| |
| // If we are null, make sure to check if we are a constructor. |
| if (isConstructor()) |
| return INamespaceConstants.public_; |
| |
| return null; |
| } |
| |
| @Override |
| public boolean hasNamespace(String namespace) |
| { |
| if (isConstructor()) |
| return namespace.compareTo(INamespaceConstants.public_) == 0; |
| |
| return super.hasNamespace(namespace); |
| } |
| |
| @Override |
| public FunctionDefinition getDefinition() |
| { |
| return (FunctionDefinition)super.getDefinition(); |
| } |
| |
| @Override |
| protected void setDefinition(IDefinition def) |
| { |
| assert def instanceof FunctionDefinition; |
| super.setDefinition(def); |
| } |
| |
| // |
| // IFunctionNode implementations |
| // |
| |
| @Override |
| public boolean isImplicit() |
| { |
| if (getParent() != null) |
| { |
| if (getParent().getParent() instanceof ClassNode) |
| { |
| ClassNode containingClass = (ClassNode)getParent().getParent(); |
| if (containingClass.getDefaultConstructorNode() == this) |
| return true; |
| } |
| else if (getParent().getParent() instanceof InterfaceNode) |
| { |
| InterfaceNode containingInterface = (InterfaceNode)getParent().getParent(); |
| if (containingInterface.getCastFunctionNode() == this) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String getQualifiedName() |
| { |
| String qualifiedName = null; |
| if (isPackageLevelFunction()) |
| { |
| IImportTarget importTarget = ASImportTarget.buildImportFromPackageName(getWorkspace(), getPackageName()); |
| qualifiedName = importTarget.getQualifiedName(getName()); |
| } |
| if (qualifiedName == null) |
| qualifiedName = getName(); |
| return qualifiedName; |
| } |
| |
| @Override |
| public String getShortName() |
| { |
| return getName(); |
| } |
| |
| @Override |
| public final ScopedBlockNode getScopedNode() |
| { |
| return contentsPart.getContents(); |
| } |
| |
| @Override |
| public FunctionClassification getFunctionClassification() |
| { |
| IScopedNode scopedNode = getScopeNode(); |
| IASNode node = scopedNode; |
| |
| if (node instanceof ICommonClassNode || node.getParent() instanceof ICommonClassNode) |
| return FunctionClassification.CLASS_MEMBER; |
| |
| if (node.getParent() instanceof InterfaceNode) |
| return FunctionClassification.INTERFACE_MEMBER; |
| |
| if (node.getParent() instanceof PackageNode) |
| return FunctionClassification.PACKAGE_MEMBER; |
| |
| if (node instanceof FileNode)// this is an include |
| return FunctionClassification.FILE_MEMBER; |
| |
| return FunctionClassification.LOCAL; |
| } |
| |
| @Override |
| public boolean isGetter() |
| { |
| return this instanceof GetterNode; |
| } |
| |
| @Override |
| public boolean isSetter() |
| { |
| return this instanceof SetterNode; |
| } |
| |
| @Override |
| public boolean isConstructor() |
| { |
| String name = getName(); |
| String returnType = getReturnType(); |
| |
| // Allow constructors that have a (bogus) return type |
| if (!returnType.equals("") && |
| !returnType.equals(name) && |
| !isAnyType() && |
| !isVoidType()) |
| { |
| return false; |
| } |
| |
| if (getParent() != null && |
| getParent().getParent() != null && |
| (getParent().getParent() instanceof ClassNode || |
| getParent().getParent() instanceof InterfaceNode)) |
| { |
| if (name.equals(((IDefinitionNode) getParent().getParent()).getShortName())) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean isCastFunction() |
| { |
| String name = getName(); |
| String returnType = getReturnType(); |
| |
| if (!returnType.equals("") && !returnType.equals(name)) |
| return false; |
| |
| if (getParent() != null && |
| getParent().getParent() != null && |
| getParent().getParent() instanceof ITypeNode) |
| { |
| if (name.equals(((ITypeNode)getParent().getParent()).getShortName())) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public ContainerNode getParametersContainerNode() |
| { |
| return contentsPart.getParametersNode(); |
| } |
| |
| @Override |
| public IParameterNode[] getParameterNodes() |
| { |
| IParameterNode[] variables = {}; |
| |
| ContainerNode arguments = contentsPart.getParametersNode(); |
| |
| if (arguments != null) |
| { |
| int argumentscount = arguments.getChildCount(); |
| variables = new IParameterNode[argumentscount]; |
| for (int i = 0; i < argumentscount; i++) |
| { |
| IASNode argument = arguments.getChild(i); |
| if (argument instanceof IParameterNode) |
| variables[i] = (IParameterNode)argument; |
| } |
| } |
| |
| return variables; |
| } |
| |
| @Override |
| public IExpressionNode getReturnTypeNode() |
| { |
| return getTypeNode(); |
| } |
| |
| @Override |
| public String getReturnType() |
| { |
| return getTypeName(); |
| } |
| |
| @Override |
| public boolean hasBody() |
| { |
| ScopedBlockNode sbn = getScopedNode(); |
| |
| return sbn.getChildCount() > 0 || |
| sbn.getContainerType() != IContainerNode.ContainerType.SYNTHESIZED; |
| } |
| |
| // |
| // Other methods |
| // |
| |
| protected IFunctionContentsPart createContentsPart() |
| { |
| return new FunctionContentsPart(); |
| } |
| |
| private void tryAddDefaultArgument() |
| { |
| FunctionDefinition def = getDefinition(); |
| ASScope funcScope = def.getContainedScope(); |
| |
| if (needsArguments && funcScope.getLocalDefinitionSetByName(IASLanguageConstants.arguments) == null) |
| { |
| // Add the arguments Array to the function scope |
| // only do this if there is not already a local property, or parameter that is named arguments |
| // and something in the function body references arguments - this should avoid creating the |
| // definition when it's not needed. |
| VariableDefinition argumentsDef = new VariableDefinition(IASLanguageConstants.arguments); |
| argumentsDef.setNamespaceReference(NamespaceDefinition.getDefaultNamespaceDefinition(funcScope)); |
| argumentsDef.setTypeReference(ReferenceFactory.builtinReference(IASLanguageConstants.BuiltinType.ARRAY)); |
| |
| argumentsDef.setImplicit(); |
| |
| funcScope.addDefinition(argumentsDef); |
| } |
| } |
| |
| private void setConstructorIfNeeded(FunctionDefinition funcDef) |
| { |
| if (isConstructor()) |
| { |
| IASNode parentParent = getParent().getParent(); |
| if( parentParent instanceof ClassNode) |
| { |
| ClassNode classNode = (ClassNode)parentParent; |
| if (classNode.getConstructorNode() == null) |
| { |
| if (nameNode instanceof IdentifierNode) |
| { |
| ((IdentifierNode)nameNode).setReferenceValue(classNode.getDefinition()); |
| classNode.constructorNode = this; |
| } |
| } |
| // if the namespace reference is private, don't change it |
| if(!(funcDef.getNamespaceReference() instanceof INamespaceDefinition.IPrivateNamespaceDefinition)) |
| { |
| funcDef.setNamespaceReference(NamespaceDefinition.getCodeModelImplicitDefinitionNamespace()); |
| } |
| } |
| else |
| { |
| funcDef.setNamespaceReference(NamespaceDefinition.getCodeModelImplicitDefinitionNamespace()); |
| } |
| } |
| } |
| |
| FunctionDefinition buildDefinition() |
| { |
| String definitionName = getName(); |
| // System.out.println("buildDefinition: " + definitionName); |
| FunctionDefinition definition = createFunctionDefinition(definitionName); |
| definition.setNode(this); |
| |
| fillInNamespaceAndModifiers(definition); |
| fillInMetadata(definition); |
| |
| // Set the return type. If a type annotation doesn't appear in the source, |
| // the return type in the definition will be "". |
| IReference returnType = typeNode != null ? typeNode.computeTypeReference() : null; |
| definition.setReturnTypeReference(returnType); |
| |
| definition.setTypeReference(ReferenceFactory.builtinReference(IASLanguageConstants.BuiltinType.FUNCTION)); |
| setConstructorIfNeeded(definition); |
| return definition; |
| } |
| |
| /** |
| * Method to create the correct Definition class - needed so we can create |
| * different Definition types for getters, setters, and plain old functions |
| * |
| * @return A Definition object to represent this function |
| */ |
| protected FunctionDefinition createFunctionDefinition(String name) |
| { |
| return new FunctionDefinition(name); |
| } |
| |
| /** |
| * Get the function keyword |
| * |
| * @return node containing the function keyword |
| */ |
| public KeywordNode getFunctionKeywordNode() |
| { |
| return contentsPart.getFunctionKeywordNode(); |
| } |
| |
| /** |
| * Determine whether this is a package-level function (i.e. a function |
| * defined in a package, as opposed to a class or some other scope) |
| * |
| * @return true if this is a package-level function |
| */ |
| public boolean isPackageLevelFunction() |
| { |
| // regular package-level function |
| IASNode parent = getParent(); |
| IASNode parent2 = parent.getParent(); |
| |
| if (parent instanceof BlockNode && parent2 instanceof PackageNode) |
| return true; |
| |
| // constructor |
| if (parent2 != null) |
| { |
| IASNode parent3 = parent2.getParent(); |
| |
| if (isConstructor() && |
| parent2 instanceof ClassNode && |
| parent3 instanceof BlockNode && |
| parent3.getParent() instanceof PackageNode) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Get the real namespace node, or null if there isn't one. Used by |
| * semantics. This differs from getNamespaceNode above, as it will not |
| * construct an implicit public namespace if one is missing |
| */ |
| public INamespaceDecorationNode getActualNamespaceNode() |
| { |
| return super.getNamespaceNode(); |
| } |
| |
| /** |
| * Is this a constructor of the specified class (assumes that this function |
| * definition is actually located in the body of the specified class) |
| */ |
| public boolean isConstructorOf(ClassNode classNode) |
| { |
| return this.equals(classNode.getConstructorNode()); |
| } |
| |
| /** |
| * Get any saved parsing problems from lazily parsing the function body. |
| * This list will be nulled out after this method is called, |
| * so we don't start leaking parser problems. |
| * |
| * @return Collection of problems encountered while parsing the function |
| * or an empty list if there were none. |
| * |
| */ |
| public Collection<ICompilerProblem> getParsingProblems() |
| { |
| if (parseProblems != null) |
| { |
| Collection<ICompilerProblem> problems = parseProblems; |
| parseProblems = null; |
| return problems; |
| } |
| |
| return Collections.emptyList(); |
| } |
| /** |
| * Build AST for the function body from the buffered function body text. |
| * <p> |
| * Make sure {@link PostProcessStep#POPULATE_SCOPE} has been applied to the |
| * containing {@code FileNode} This method always populate scopes of the |
| * rebuilt function node. If the scopes for the containing nodes weren't |
| * initialized, the rebuilt scopes can attach itself to it's parent. |
| */ |
| public final void parseFunctionBody(final Collection<ICompilerProblem> problems) |
| { |
| if (!isBodyDeferred) |
| return; |
| |
| deferredBodyParsingLock.lock(); |
| try |
| { |
| deferredBodyParsingReferenceCount++; |
| |
| assert problems != null : "Problems collection can't be null"; |
| |
| final ScopedBlockNode contents = contentsPart.getContents(); |
| assert contents != null : "Function body node can't be null: function " + getName(); |
| |
| // Only re-parse if the function body text is not empty, and the |
| // function body node doesn't have any children. |
| if (contents.getChildCount() > 0) |
| return; |
| assert deferredBodyParsingReferenceCount == 1; |
| |
| assert openT != null : "Expected '{' token."; |
| |
| final String sourcePath = getSourcePath(); |
| assert sourcePath != null && !sourcePath.isEmpty() : "Souce path not set."; |
| |
| final FileNode fileNode = (FileNode)getAncestorOfType(FileNode.class); |
| assert fileNode != null : "FileNode not found: function " + getName(); |
| final IWorkspace workspace = fileNode.getWorkspace(); |
| |
| ASFileScope fileScope = fileNode.getFileScope(); |
| fileScope.addParsedFunctionBodies(this); |
| |
| try |
| { |
| // select function body source |
| final Reader sourceReader; |
| if (functionBodyText != null) |
| { |
| // from cached body text |
| sourceReader = new StringReader(functionBodyText); |
| } |
| else |
| { |
| // from file using offset |
| if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE) |
| System.out.println("FunctionNode waiting for lock in parseFunctionBody"); |
| sourceReader = workspace.getFileSpecification(sourcePath).createReader(); |
| if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE) |
| System.out.println("FunctionNode done with lock in parseFunctionBody"); |
| sourceReader.skip(openT.getLocalEnd()); |
| } |
| |
| assert !anyNonParametersInScope(contents); |
| |
| // rebuild function body AST |
| |
| // The incoming "problems" collection might be under modifications from |
| // other code generation threads. In order to filter problems after parsing, |
| // a local problem collection is created and added to the main problem |
| // collection later. |
| final List<ICompilerProblem> functionLocalProblems = new ArrayList<ICompilerProblem>(); |
| ASParser.parseFunctionBody( |
| contents, |
| sourceReader, |
| sourcePath, |
| openT, |
| functionLocalProblems, |
| workspace, |
| fileNode, |
| configProcessor); |
| filterObsoleteProblems(fileNode, functionLocalProblems); |
| problems.addAll(functionLocalProblems); |
| |
| // dispose cached function body info |
| |
| functionBodyText = null; |
| |
| // We should release "openT" as well. However, incremental compilation |
| // needs this to redo code-generation. |
| // openT = null; |
| |
| // connect function and its body scope |
| final EnumSet<PostProcessStep> postProcess = EnumSet.of( |
| PostProcessStep.CALCULATE_OFFSETS, |
| PostProcessStep.POPULATE_SCOPE, |
| PostProcessStep.RECONNECT_DEFINITIONS); |
| |
| problems.addAll(contents.runPostProcess(postProcess, contents.getASScope())); |
| |
| // add implicit "arguments" argument to the local scope |
| tryAddDefaultArgument(); |
| } |
| catch (IOException e) |
| { |
| problems.add(new InternalCompilerProblem2(this.getSourcePath(), e, "function body parser")); |
| } |
| } |
| finally |
| { |
| deferredBodyParsingLock.unlock(); |
| } |
| } |
| |
| /** |
| * {@link BaseASParser} is stateful. The state is essential in compiler |
| * problem creation. However, when re-parsing a deferred function body, the |
| * parser state can't be fully restored. As a result, some compiler problems |
| * can be obsolete. This function removes those unwanted problems. |
| * |
| * @param fileNode AST root node. |
| * @param problems compiler problems found in the deferred function body. |
| */ |
| private void filterObsoleteProblems(FileNode fileNode, Collection<ICompilerProblem> localProblems) |
| { |
| final int functionStartLine = this.getLine(); |
| final Collection<ICompilerProblem> problems = fileNode.getProblems(); |
| final Collection<ICompilerProblem> filteredLocalProblems = filter( |
| localProblems, |
| and(problemAtLine(functionStartLine), problemOfType(CanNotInsertSemicolonProblem.class))); |
| |
| // If the function signature has syntax error, the first "unterminated statement" problem |
| // from function body is then obsolete. |
| if (!filteredLocalProblems.isEmpty()) |
| { |
| final Collection<ICompilerProblem> functionSignatureProblems = |
| filter(problems, problemAtLine(functionStartLine)); |
| if (!functionSignatureProblems.isEmpty()) |
| { |
| localProblems.removeAll(filteredLocalProblems); |
| } |
| } |
| } |
| |
| /** |
| * IFilter {@link ICompilerProblem} collections by line number. |
| */ |
| private static Predicate<ICompilerProblem> problemAtLine(final int line) |
| { |
| return new Predicate<ICompilerProblem>() |
| { |
| @Override |
| public boolean apply(ICompilerProblem problem) |
| { |
| return problem.getLine() == line; |
| } |
| @Override |
| public boolean test(ICompilerProblem input) |
| { |
| return apply(input); |
| } |
| }; |
| } |
| |
| /** |
| * IFilter {@link ICompilerProblem} collections by class. |
| */ |
| private static Predicate<ICompilerProblem> problemOfType(final Class<? extends ICompilerProblem> problemClass) |
| { |
| return new Predicate<ICompilerProblem>() |
| { |
| @Override |
| public boolean apply(ICompilerProblem problem) |
| { |
| return problemClass.isInstance(problem); |
| } |
| @Override |
| public boolean test(ICompilerProblem input) |
| { |
| return apply(input); |
| } |
| }; |
| } |
| |
| private static boolean anyNonParametersInScope(ScopedBlockNode contents) |
| { |
| IASScope sc = contents.getScope(); |
| Collection<IDefinition> ldfs = sc.getAllLocalDefinitions(); |
| for (IDefinition def : ldfs) |
| { |
| if (!(def instanceof IParameterDefinition)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Delete all children nodes in a function body. |
| */ |
| public final void discardFunctionBody() |
| { |
| if (!isBodyDeferred || containsLocalFunctions()) |
| return; |
| |
| deferredBodyParsingLock.lock(); |
| try |
| { |
| deferredBodyParsingReferenceCount--; |
| // only discard the body once there are 0 reference to it |
| if (deferredBodyParsingReferenceCount > 0) |
| return; |
| |
| final ScopedBlockNode contents = getScopedNode(); |
| if (contents.getChildCount() > 0) |
| { |
| final FileNode fileNode = (FileNode)getAncestorOfType(FileNode.class); |
| ASFileScope fileScope = fileNode.getFileScope(); |
| fileScope.removeParsedFunctionBodies(this); |
| |
| contents.removeAllChildren(); |
| |
| // Now we need to remove all the definitions in this function scope, except |
| // we keep the parameters. This is because the initial "skeleton parse" goes as |
| // far as the parameters. |
| // So when we throw away the body, we still need to keep the parameter definitions |
| IASScope functionScope = contents.getScope(); |
| Collection<IDefinition> localDefs = functionScope.getAllLocalDefinitions(); |
| for (IDefinition def : localDefs) |
| { |
| if (! (def instanceof IParameterDefinition)) |
| { |
| ASScope asScope = (ASScope)functionScope; |
| asScope.removeDefinition(def); |
| } |
| } |
| } |
| assert (contents.getScope() == null) || (!anyNonParametersInScope(contents)); |
| } |
| finally |
| { |
| deferredBodyParsingLock.unlock(); |
| } |
| } |
| |
| public final boolean hasBeenParsed() |
| { |
| if (!isBodyDeferred) |
| return true; |
| |
| deferredBodyParsingLock.lock(); |
| try |
| { |
| return deferredBodyParsingReferenceCount > 0; |
| } |
| finally |
| { |
| deferredBodyParsingLock.unlock(); |
| } |
| } |
| |
| /** |
| * Store the function body text on the function node so that the AST nodes |
| * can be rebuilt later. |
| */ |
| public final void setFunctionBodyInfo(ASToken openT, ASToken lastTokenInBody, |
| ConfigProcessor configProcessor, |
| StringBuilder bodyCache) |
| { |
| assert openT != null : "Open curly token can't be null"; |
| assert openT.getType() == ASTokenTypes.TOKEN_BLOCK_OPEN : "Expected '{' token."; |
| assert lastTokenInBody != null : "Last token in function body can't be null."; |
| assert configProcessor != null : "Project config variables can't be null."; |
| |
| this.openT = openT.clone(); |
| this.configProcessor = configProcessor; |
| this.isBodyDeferred = true; |
| |
| if (bodyCache == null) |
| this.functionBodyText = null; |
| else |
| this.functionBodyText = bodyCache.toString(); |
| } |
| |
| private ArrayList<IFunctionNode> localFunctions; |
| |
| @Override |
| public List<IFunctionNode> getLocalFunctions() |
| { |
| return localFunctions; |
| } |
| |
| @Override |
| public boolean containsLocalFunctions() |
| { |
| return localFunctions != null; |
| } |
| |
| @Override |
| public void rememberLocalFunction(IFunctionNode value) |
| { |
| if (localFunctions == null) |
| localFunctions = new ArrayList<IFunctionNode>(); |
| |
| localFunctions.add(value); |
| } |
| } |