blob: 54c836c3b13bc22afbdb0fb2ff237033b180368f [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.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);
}
}