blob: 3d30c7debca389cd5bd9783fc762e6523b204430 [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.parsing.as;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import antlr.Token;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.semantics.Namespace;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.internal.definitions.ConstantDefinition;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.parsing.as.ConfigCompilationUnit.ConfigFileNode;
import org.apache.royale.compiler.internal.parsing.as.ConfigProcessor.ConfigProject.ConfigProjectScope;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.projects.ISourceFileHandler;
import org.apache.royale.compiler.internal.projects.DefinitionPriority.BasePriority;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.ConfigConstNode;
import org.apache.royale.compiler.internal.tree.as.ConfigExpressionNode;
import org.apache.royale.compiler.internal.tree.as.ConfigNamespaceNode;
import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
import org.apache.royale.compiler.internal.tree.as.LiteralNode;
import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode;
import org.apache.royale.compiler.internal.tree.as.NamespaceNode;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.NumericLiteralNode;
import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.parsing.IASToken;
import org.apache.royale.compiler.problems.CannotResolveConfigExpressionProblem;
import org.apache.royale.compiler.problems.CannotResolveProjectLevelConfigExpressionProblem;
import org.apache.royale.compiler.problems.ConflictingNameInNamespaceProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.InternalCompilerProblem2;
import org.apache.royale.compiler.problems.NonConstantConfigInitProblem;
import org.apache.royale.compiler.problems.UndefinedConfigNamespaceProblem;
import org.apache.royale.compiler.scopes.IDefinitionSet;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.requests.ISyntaxTreeRequestResult;
import org.apache.royale.compiler.workspaces.IWorkspace;
import org.apache.royale.utils.FilenameNormalization;
import com.google.common.collect.Iterables;
/**
* Processor handles config information found by the parser and that is set on
* the current project
*/
public class ConfigProcessor
{
/**
* Used as a fake project to facilitate type lookup
*/
final class ConfigProject extends org.apache.royale.compiler.internal.projects.ASProject
{
/**
* Scope that allows direct addition of IDefinitions without adding
* compilation units
*/
final class ConfigProjectScope extends ASProjectScope
{
/**
* @param project
*/
private ConfigProjectScope(CompilerProject project)
{
super(project);
}
public void addConfigDefinition(IDefinition definition)
{
addDefinitionToStore(definition);
}
}
private ConfigProject(IWorkspace w)
{
super((Workspace)w, true);
getSourceCompilationUnitFactory().addHandler(new ISourceFileHandler()
{
@Override
public String[] getExtensions()
{
return new String[] {"config"};
}
@Override
public ICompilationUnit createCompilationUnit(CompilerProject project, String path,
BasePriority priority, int order, String qname, String locale)
{
return new ConfigCompilationUnit(backingProject, path);
}
@Override
public boolean needCompilationUnit(CompilerProject project, String path, String qname, String locale)
{
return true;
}
@Override
public boolean canCreateInvisibleCompilationUnit()
{
return false;
}
});
}
@Override
protected ASProjectScope initProjectScope(CompilerProject project)
{
return new ConfigProjectScope(project);
}
@Override
public ConfigProjectScope getScope()
{
return (ConfigProjectScope)super.getScope();
}
public void removeCU(ConfigCompilationUnit cu)
{
removeCompilationUnits(Collections.<ICompilationUnit> singletonList(cu));
}
@Override
public boolean getAllowPrivateNameConflicts() {
// TODO Auto-generated method stub
return false;
}
}
/**
* Scope block that we add all of our config information to
*/
private ScopedBlockNode configScope;
private BaseASParser parser;
/**
* Flag to keep track of if we should report errors or not If we're
* transferring constants from the project to an individual config
* processor, then we don't need to report the errors as they were already
* reported as errors on the project.
*/
private boolean transferringConstants;
/**
* Fake project we use to faciliate type lookup for expressions
*/
private ConfigProject backingProject;
/**
* Project variables
*/
private IProjectConfigVariables variables;
/**
* Our fake config compilation unit we use to make the type/project system
* happy
*/
private ConfigCompilationUnit configUnit;
private HashSet<String> configNames;
private final IWorkspace workspace;
ConfigProcessor(IWorkspace workspace, BaseASParser parser)
{
this.parser = parser;
configNames = new HashSet<String>();
configNames.add(IASLanguageConstants.DEFAULT_CONFIG_NAME);
this.workspace = workspace;
}
/**
* Sets the {@link IProjectConfigVariables} that will be used to alter the
* shape of the tree we are building
*
* @param variables {@link IProjectConfigVariables} or null
*/
public void connect(IProjectConfigVariables variables)
{
this.variables = variables;
if (variables != null)
{
List<String> namespaces = variables.getConfigNamespaceNames();
for (String namespace : namespaces)
{
configNames.add(namespace);
}
}
}
/**
* Disconnects us from the parser, cleaning up any state that was built
*/
public void disconnect()
{
if (configUnit != null)
backingProject.removeCU(configUnit);
if (backingProject != null)
backingProject = null;
parser = null;
}
public final boolean isConfigNamespace(final String name)
{
return configNames.contains(name);
}
private final void initConfigStructures()
{
if (backingProject == null)
{
backingProject = new ConfigProject(workspace);
String configFileName = FilenameNormalization.normalize("config" + Integer.toString(hashCode()) + ".config");
try
{
backingProject.setIncludeSources(new File[] {new File(configFileName)});
}
catch (InterruptedException e1)
{
//ignore this
}
Collection<ICompilationUnit> units = backingProject.getCompilationUnits(configFileName);
assert units.size() == 1 && Iterables.getOnlyElement(units) instanceof ConfigCompilationUnit;
try
{
transferringConstants = true;
ISyntaxTreeRequestResult treeResult = Iterables.getOnlyElement(units).getSyntaxTreeRequest().get();
configScope = ((ConfigFileNode)treeResult.getAST()).getTargetConfigScope();
addConditionalCompilationNamespace(new ConfigNamespaceNode(new IdentifierNode(IASLanguageConstants.DEFAULT_CONFIG_NAME, (Token)null)));
if (variables != null)
{
if (variables != null)
{
List<IDefinition> definitions = variables.getRequiredDefinitions();
ConfigProjectScope scope = backingProject.getScope();
for (IDefinition def : definitions)
{
if (def != null && scope.getLocalDefinitionSetByName(def.getQualifiedName()) == null)
{
scope.addConfigDefinition(def);
}
}
}
List<ConfigNamespaceNode> namespaces = variables.getConfigNamespaces();
for (ConfigNamespaceNode ns : namespaces)
{
addConditionalCompilationNamespace(ns);
}
List<ConfigConstNode> vars = variables.getConfigVariables();
for (ConfigConstNode var : vars)
{
addConfigConstNode(var);
}
}
}
catch (InterruptedException e)
{
ICompilerProblem problem = new InternalCompilerProblem2(parser.getFilename(), e, "ConfigProcessor");
addProblem(problem);
}
finally
{
transferringConstants = false;
}
}
}
/**
* Returns any children created by this config processor
*
* @return children, or an empty array
*/
public IASNode[] getConfigChildren()
{
if (configScope != null)
{
final int childCount = configScope.getChildCount();
ArrayList<IASNode> children = new ArrayList<IASNode>(childCount);
for (int i = 0; i < childCount; i++)
{
NodeBase child = (NodeBase)configScope.getChild(i);
((NodeBase)child).setParent(null);
if (child instanceof ConfigConstNode)
{
((ConfigConstNode)child).reset();
}
children.add(child);
}
return children.toArray(new IASNode[0]);
}
return new IASNode[0];
}
/**
* Adds a name that is recognized as a config name for conditional
* compilation
*/
public boolean addConditionalCompilationNamespace(NamespaceNode node)
{
initConfigStructures();
IDefinitionSet name = configScope.getASScope().getLocalDefinitionSetByName(node.getName());
if (name != null)
{
//allow redefinition of config namespace - matches ASC
return true;
}
else
{
node.normalize(true);
configScope.addItemAfterNormalization(node);
configScope.getASScope().addDefinition(node.getDefinition());
configNames.add(node.getName());
return true;
}
}
/**
* Adds a {@link ConfigConstNode} to our internal tree. Will return true if
* this item is unique
*
* @param node the {@link ConfigConstNode} that we have encountered while
* parsing.
* @return true if this is not a redefinition of a config name
*/
public boolean addConfigConstNode(ConfigConstNode node)
{
initConfigStructures();
node.normalize(true);
IdentifierNode configNamespaceNode = (IdentifierNode)node.getNamespaceNode();
IDefinitionSet set = configScope.getASScope().getLocalDefinitionSetByName(configNamespaceNode.getName());
if (set == null)
{
IIdentifierNode namespaceNode = (IdentifierNode)node.getNamespaceNode();
ICompilerProblem problem = new UndefinedConfigNamespaceProblem(namespaceNode, namespaceNode.getName());
addProblem(problem);
return false;
}
configScope.addItemAfterNormalization(node);
DefinitionBase constDef = node.getDefinition();
configScope.getASScope().addDefinition(constDef);
if (constDef instanceof ConstantDefinition)
{
ConstantDefinition def = (ConstantDefinition)constDef;
Object value = def.resolveValue(backingProject);
if (value == ConfigConstNode.UNKNOWN_VALUE)
{
if (def instanceof ConfigConstNode.ConfigDefinition)
{
ConfigConstNode.ConfigDefinition cdef = (ConfigConstNode.ConfigDefinition)def;
IExpressionNode initializer = cdef.getInitializer();
if (initializer.getNodeID() != ASTNodeID.MemberAccessExpressionID &&
initializer.getNodeID() != ASTNodeID.IdentifierID)
{
// Get the real source node for the problem.
// If there isn't one, then don't make a problem - assume
// someone else already found the cause and logged it.
IASNode problemLocationNode = node.getAssignedValueNode();
if (problemLocationNode != null)
{
ICompilerProblem problem = new NonConstantConfigInitProblem(
problemLocationNode);
addProblem(problem);
}
}
}
}
}
// Check for redeclaration
// Config vars don't care about MultiDefinitionType.MANY vs. AMBIGUOUS
// and it's not possible for them to shadow params, so we only have to check if
// the multi type is not NONE
if (SemanticUtils.getMultiDefinitionType(constDef, backingProject) != SemanticUtils.MultiDefinitionType.NONE)
{
addProblem(new ConflictingNameInNamespaceProblem(node, constDef.getBaseName(), node.getNamespace()));
return false;
}
return true;
}
/**
* Helper method to add a problem
*
* @param problem Problem to add
*/
private void addProblem(ICompilerProblem problem)
{
// If we're transferring the constants to a particular parser, then
// the problems have already been reported via the project
// don't report them again to avoid dupes
if (!transferringConstants)
parser.addProblem(problem);
}
/**
* turns a config expression node into a Java Interger, Boolean, etc...
* creates a compiler problem is unable to evaluate
*/
protected Object evaluateConstNodeExpressionToJavaObject(ConfigExpressionNode node)
{
initConfigStructures();
node.normalize(true);
configScope.addItemAfterNormalization(node);
Object result = node.resolveConfigValue(backingProject);
if (result == null || result == ConfigConstNode.UNKNOWN_VALUE)
{
// try to allow simple memberaccessexpressions as well (a.b)
IDefinition definition = node.resolve(backingProject);
if (definition instanceof ConfigConstNode.ConfigDefinition)
{
ConfigConstNode.ConfigDefinition def = (ConfigConstNode.ConfigDefinition)definition;
IExpressionNode initializer = def.getInitializer();
if (initializer.getNodeID() == ASTNodeID.MemberAccessExpressionID ||
initializer.getNodeID() == ASTNodeID.IdentifierID)
return initializer;
}
// If we can't get a value, log an error. If we are are processing System variables, then don't
// use the problem type that requires a "site", as we don't know what it is
ICompilerProblem problem = isFromProjectConfigVariables() ?
new CannotResolveProjectLevelConfigExpressionProblem(node.getConfigValue()) :
new CannotResolveConfigExpressionProblem(node, node.getConfigValue());
addProblem(problem);
}
return result;
}
/**
* Returns true if we are this config processor was is helping to parse
* project variables, like those from the command line.
*/
private boolean isFromProjectConfigVariables()
{
return this.parser.isParsingProjectConfigVariables();
}
/**
* turns a config expression into a synthesize LiteralNode
*/
protected IASNode evaluateConstNodeExpression(ConfigExpressionNode node)
{
final Object result = evaluateConstNodeExpressionToJavaObject(node);
if (result instanceof Boolean)
{
LiteralNode literalNode = new LiteralNode(LiteralType.BOOLEAN, ((Boolean)result).toString());
literalNode.setSynthetic(true);
return literalNode;
}
else if (result instanceof String)
{
LiteralNode literalNode = new LiteralNode(LiteralType.STRING, (String)result);
literalNode.setSynthetic(true);
return literalNode;
}
else if (result instanceof Double ||
result instanceof Integer ||
result instanceof Long)
{
LiteralNode literalNode = new NumericLiteralNode(result.toString());
literalNode.setSynthetic(true);
return literalNode;
}
else if (result == ABCConstants.NULL_VALUE)
{
// Handle 'null'
LiteralNode literalNode = new LiteralNode(LiteralType.NULL, IASLanguageConstants.Null);
literalNode.setSynthetic(true);
return literalNode;
}
else if (result instanceof MemberAccessExpressionNode)
{
MemberAccessExpressionNode mae = (MemberAccessExpressionNode)result;
return mae;
}
else if (result instanceof IdentifierNode)
{
IdentifierNode id = (IdentifierNode)result;
return id;
}
else if (result instanceof Namespace)
{
Namespace ns = (Namespace)result;
String nsName = ns.getName();
if (nsName.length() == 0)
nsName = "public";
return new IdentifierNode(nsName);
}
return null;
}
/**
* Bind the configurations to a parser.
*
* @param parser AS parser.
*/
public final void setParser(BaseASParser parser)
{
this.parser = parser;
}
/**
* Detach the parser if it's currently bound to this processor.
*
* @param parser AS parser.
*/
public final void detachParser(BaseASParser parser)
{
if (this.parser == parser)
this.parser = null;
}
}