/*
* Copyright 2003-2012 JetBrains s.r.o.
*
* Licensed 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 jetbrains.mps.newTypesystem.context.component;
import gnu.trove.THashSet;
import jetbrains.mps.errors.IErrorReporter;
import jetbrains.mps.lang.typesystem.runtime.InferenceRule_Runtime;
import jetbrains.mps.lang.typesystem.runtime.IsApplicableStatus;
import jetbrains.mps.lang.typesystem.runtime.SubstituteType_Runtime;
import jetbrains.mps.newTypesystem.context.typechecking.BaseTypechecking;
import jetbrains.mps.newTypesystem.state.State;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.typesystem.inference.TypeSubstitution;
import jetbrains.mps.typesystemEngine.util.TypeSystemUtil;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.util.SNodeOperations;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.typesystem.inference.TypeChecker;
import jetbrains.mps.util.Pair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* User: fyodor
* Date: 11/12/12
*/
/*package*/ public class SimpleTypecheckingComponent<STATE extends State> {
private final STATE myState;
protected Queue<SNode> myQueue = new LinkedList<SNode>();
protected boolean myIsChecked = false;
protected BaseTypechecking<?, ?> myTypechecking;
protected Set<SNode> myNodes = new THashSet<SNode>();
protected Set<SNode> myFullyCheckedNodes = new THashSet<SNode>(); //nodes which are checked with their children
protected Set<SNode> myPartlyCheckedNodes = new THashSet<SNode>(); // nodes which are checked themselves but not children
public SimpleTypecheckingComponent(STATE state, BaseTypechecking component) {
myState = state;
myTypechecking = component;
}
protected boolean isIncrementalMode() {
return false;
}
//returns true if something was invalidated
protected boolean doInvalidate() {
return false;
}
protected void invalidateNodeTypeSystem(SNode node, boolean typeWillBeRecalculated) {
myPartlyCheckedNodes.remove(node);
myFullyCheckedNodes.remove(node);
myState.clearNode(node);
}
protected void performActionsAfterChecking() {
// do nothing
}
public SNode computeTypesForNode_special(SNode initialNode, Collection<SNode> givenAdditionalNodes) {
return computeTypesForNode_special_(initialNode, givenAdditionalNodes);
}
protected void setTargetNode(SNode initialNode) {
assert false;
}
public Map<SNode, List<IErrorReporter>> getNodesToErrorsMap() {
return Collections.emptyMap();
}
protected void clearState() {
myState.clear(true);
}
public void addNodeToFrontier(SNode node) {
if (node == null) return;
myQueue.add(node);
}
public void computeTypes(boolean refreshTypes) {
computeTypes(getTypechecking().getNode(), refreshTypes, true, Collections.<SNode>emptyList(), true, null);
}
protected void computeTypes(SNode nodeToCheck, boolean refreshTypes, boolean forceChildrenCheck, Collection<SNode> additionalNodes, boolean finalExpansion, SNode initialNode) {
if (refreshTypes) {
clear();
if (initialNode != null) {
setTargetNode(initialNode);
}
} else {
myState.clearStateObjects();
doInvalidate();
myPartlyCheckedNodes.addAll(myFullyCheckedNodes);
myFullyCheckedNodes.clear();
}
computeTypesSpecial(nodeToCheck, forceChildrenCheck, additionalNodes, finalExpansion, initialNode);
performActionsAfterChecking();
myState.performActionsAfterChecking(); }
public void setChecked() {
myIsChecked = true;
}
public boolean isChecked() {
return myIsChecked && !doInvalidate();
}
protected BaseTypechecking getTypechecking() {
return myTypechecking;
}
public void solveInequalitiesAndExpandTypes(boolean finalExpansion) {
myState.solveInequalities();
myState.expandAll(myNodes, finalExpansion);
myNodes.clear();
}
protected AccessTracking createAccessTracking() {
return new AccessTracking();
}
protected void applyRulesToNode(SNode node, List<Pair<InferenceRule_Runtime, IsApplicableStatus>> newRules) {
for (Pair<InferenceRule_Runtime, IsApplicableStatus> rule : newRules) {
myState.applyRuleToNode(node, rule.o1, rule.o2);
}
}
/**
* Search for and apply the inference rules to the given node.
*
* In case the node has node attributes, also search for inference rules for these attributes and apply them *before* rules for the
* node given.
*
* @param node
* @return
*/
protected boolean applyRulesToNode(SNode node) {
final List<Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>>> nodesAndRules = new ArrayList<Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>>>();
if (!collectNodesAndRules(node, nodesAndRules)) return false;
for (Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>> pair : nodesAndRules) {
applyRulesToNode(pair.o1, pair.o2);
}
return true;
}
@NotNull
protected SConcept getNodeAttributeConcept() {
return MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x2eb1ad060897da54L, "jetbrains.mps.lang.core.structure.NodeAttribute");
}
@NotNull
protected SContainmentLink getSmodelAttributeRole() {
return MetaAdapterFactory.getContainmentLink(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x10802efe25aL, 0x47bf8397520e5942L, "smodelAttribute");
}
private boolean isNodeAttribute(SNode sNode) {
boolean conceptMatches = sNode.getConcept().isSubConceptOf(getNodeAttributeConcept());
return conceptMatches && getSmodelAttributeRole().equals(sNode.getContainmentLink());
}
protected boolean collectNodesAndRules(SNode node, List<Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>>> nodesAndRules) {
for (SNode nodeOrAttr : myTypechecking.nodesToApplyRulesTo(node)) {
List<Pair<InferenceRule_Runtime, IsApplicableStatus>> rules = TypeChecker.getInstance().getRulesManager().getInferenceRules(nodeOrAttr);
if (rules != null && !rules.isEmpty()) {
nodesAndRules.add(new Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>>(nodeOrAttr, rules));
// check if the last rule applicable to an attribute supercedes the rules that may follow (last one wins)
// this has no effect if we're looking at the attributed node
Pair<InferenceRule_Runtime, IsApplicableStatus> lastPair = rules.get(rules.size() - 1);
if (lastPair.o1.supercedesAttributed(nodeOrAttr, lastPair.o2)) {
break;
}
}
}
return !nodesAndRules.isEmpty();
}
public SNode getType(SNode node) {
if (myFullyCheckedNodes.contains(node)) {
return getRawTypeFromContext(node);
}
return null;
}
private SNode getRawTypeFromContext(SNode node) {
return myState.getTypeCheckingContext().getTypeDontCheck(node);
// synchronized (TYPECHECKING_LOCK) {
// return myState.getNodeMaps().getType(node);
// }
}
private void drainQueue(boolean forceChildrenCheck, SNode targetNode, AccessTracking accessTracking) {
for (SNode sNode = myQueue.poll(); sNode != null; sNode = myQueue.poll()) {
if (myFullyCheckedNodes.contains(sNode) || !TypeSystemUtil.shouldApplyTypeSystemRules(sNode)) {
continue;
}
Set<SNode> candidatesForFrontier = new LinkedHashSet<SNode>();
if (forceChildrenCheck) {
candidatesForFrontier.addAll(IterableUtil.asCollection(sNode.getChildren()));
}
for (SNode candidate : candidatesForFrontier) {
if (candidate == null || myFullyCheckedNodes.contains(candidate)) continue;
myQueue.add(candidate);
}
if (isNodeAttribute(sNode)) {
// attributes are processed together with the attributed nodes
myQueue.add(sNode.getParent());
} else if (!myPartlyCheckedNodes.contains(sNode)) {
applyRulesAndTrackAccess(accessTracking, sNode);
myPartlyCheckedNodes.add(sNode);
}
myFullyCheckedNodes.add(sNode);
if (typeCalculated(targetNode) != null) return;
}
}
private void applyRulesAndTrackAccess(AccessTracking accessTracking, SNode sNode) {
accessTracking.installReadListeners();
boolean typeAffected = false;
try {
myNodes.add(sNode);
typeAffected = applyRulesToNode(sNode);
} finally {
accessTracking.removeReadListeners();
}
accessTracking.postProcess(sNode, typeAffected);
}
protected SNode typeCalculated(SNode initialNode) {
return null;
}
protected void computeTypesSpecial(SNode nodeToCheck, boolean forceChildrenCheck, Collection<SNode> additionalNodes, boolean finalExpansion, SNode initialNode) {
AccessTracking accessTracking = createAccessTracking();
if (nodeToCheck != null) {
myQueue.add(nodeToCheck);
myQueue.addAll(additionalNodes);
}
while (!myQueue.isEmpty()) {
drainQueue(forceChildrenCheck, initialNode, accessTracking);
if (typeCalculated(initialNode) != null) return;
solveInequalitiesAndExpandTypes(finalExpansion);
}
}
protected SNode computeTypesForNode_special_(SNode initialNode, Collection<SNode> givenAdditionalNodes) {
assert myFullyCheckedNodes.isEmpty();
return computeTypesForNode_special__(initialNode, givenAdditionalNodes);
}
protected SNode computeTypesForNode_special__(SNode initialNode, Collection<SNode> givenAdditionalNodes) {
long start = System.currentTimeMillis();
setTarget(initialNode);
if (initialNode == null) return null;
computeTypesSpecial(initialNode, false, givenAdditionalNodes, false, initialNode);
SNode type = typeCalculated(initialNode);
if (type != null) return type;
if (initialNode.getModel() != null && initialNode.getParent() == null) {
computeTypes(initialNode, initialNode);
return getType(initialNode);
}
TypeSystemComponent.LOG.debug("No typesystem rule for " + SNodeOperations.getDebugText(initialNode) + " in root " + initialNode.getContainingRoot() + ": type calculation took " + (System.currentTimeMillis() - start) + " ms", new Throwable(), new jetbrains.mps.smodel.SNodePointer(initialNode));
return null;
}
protected void computeTypes(SNode initialNode, SNode node) {
/*do nothing*/
}
protected void setTarget(SNode initialNode) {
/*do nothing*/
}
public void clear() {
}
/*package*/ STATE getState() {
return myState;
}
protected static class AccessTracking {
protected void installReadListeners() {}
protected void removeReadListeners() {}
protected void postProcess(SNode sNode, boolean typeAffected){}
}
public TypeSubstitution lookupSubstitution(SNode origNode, TypeCheckingContext typeCheckingContext) {
for (SNode ruleNode : myTypechecking.nodesToApplyRulesTo(origNode)) {
for (Pair<SubstituteType_Runtime, IsApplicableStatus> rule_status : substituteTypeRules(ruleNode)) {
if(rule_status.o2.isApplicable()) {
TypeSubstitution subs = rule_status.o1.substitution(ruleNode, origNode, typeCheckingContext, rule_status.o2);
if (subs != null && subs.isValid()) {
return subs;
}
}
}
}
return null;
}
private List<Pair<SubstituteType_Runtime, IsApplicableStatus>> substituteTypeRules(SNode test) {
return TypeChecker.getInstance().getRulesManager().getSubstituteTypeRules(test);
}
}