Package org.intellij.grammar.generator

Source Code of org.intellij.grammar.generator.RuleGraphHelper

/*
* Copyright 2011-2014 Gregory Shrago
*
* 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 org.intellij.grammar.generator;

import com.intellij.lang.Language;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashMap;
import gnu.trove.TObjectHashingStrategy;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.analysis.BnfFirstNextAnalyzer;
import org.intellij.grammar.psi.*;
import org.intellij.grammar.psi.impl.GrammarUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.util.containers.ContainerUtil.newTroveSet;
import static org.intellij.grammar.generator.ParserGeneratorUtil.*;
import static org.intellij.grammar.generator.RuleGraphHelper.Cardinality.*;
import static org.intellij.grammar.psi.impl.GrammarUtil.collectExtraArguments;
import static org.intellij.grammar.psi.impl.GrammarUtil.nextOrParent;

/**
* @author gregory
*         Date: 16.07.11 10:41
*/
public class RuleGraphHelper {
  private static final TObjectHashingStrategy<PsiElement> CARDINALITY_HASHING_STRATEGY = new TObjectHashingStrategy<PsiElement>() {
    @Override
    public int computeHashCode(PsiElement e) {
      if (e instanceof BnfReferenceOrToken || e instanceof BnfLiteralExpression) {
        return e.getText().hashCode();
      }
      return CANONICAL.computeHashCode(e);
    }

    @Override
    public boolean equals(PsiElement e1, PsiElement e2) {
      if (e1 instanceof BnfReferenceOrToken && e2 instanceof BnfReferenceOrToken ||
          e1 instanceof BnfLiteralExpression && e2 instanceof BnfLiteralExpression) {
        return e1.getText().equals(e2.getText());
      }
      return CANONICAL.equals(e1, e2);
    }
  };
  private final BnfFile myFile;
  private final MultiMap<BnfRule, BnfRule> myRuleExtendsMap;
  private final MultiMap<BnfRule,BnfRule> myRulesGraph = newMultiMap();
  private final Map<BnfRule, Map<PsiElement, Cardinality>> myRuleContentsMap = ContainerUtil.newTroveMap();
  private final MultiMap<BnfRule, PsiElement> myRulesCollapseMap = newMultiMap();
  private final Set<BnfRule> myRulesWithTokens = ContainerUtil.newTroveSet();
  private final Map<String, PsiElement> myExternalElements = ContainerUtil.newTroveMap();

  private static final LeafPsiElement LEFT_MARKER = new LeafPsiElement(new IElementType("LEFT_MARKER", Language.ANY, false) {}, "LEFT_MARKER");
  private static final IElementType EXTERNAL_TYPE = new IElementType("EXTERNAL_TYPE", Language.ANY, false) {};

  public static String getCardinalityText(Cardinality cardinality) {
    if (cardinality == AT_LEAST_ONE) {
      return "+";
    }
    else if (cardinality == ANY_NUMBER) {
      return "*";
    }
    else if (cardinality == OPTIONAL) {
      return "?";
    }
    return "";
  }

  public enum Cardinality {
    NONE, OPTIONAL, REQUIRED, AT_LEAST_ONE, ANY_NUMBER;

    public boolean optional() {
      return this == OPTIONAL || this == ANY_NUMBER || this == NONE;
    }

    public boolean many() {
      return this == AT_LEAST_ONE || this == ANY_NUMBER;
    }

    public Cardinality single() {
      return this == AT_LEAST_ONE? REQUIRED : this == ANY_NUMBER? OPTIONAL : this;
    }

    public static Cardinality fromNodeType(IElementType type) {
      if (type == BnfTypes.BNF_OP_OPT) {
        return OPTIONAL;
      }
      else if (type == BnfTypes.BNF_SEQUENCE || type == BnfTypes.BNF_REFERENCE_OR_TOKEN) {
        return REQUIRED;
      }
      else if (type == BnfTypes.BNF_CHOICE) {
        return OPTIONAL;
      }
      else if (type == BnfTypes.BNF_OP_ONEMORE) {
        return AT_LEAST_ONE;
      }
      else if (type == BnfTypes.BNF_OP_ZEROMORE) {
        return ANY_NUMBER;
      }
      else {
        throw new AssertionError("unexpected: " + type);
      }
    }

    public Cardinality and(Cardinality c) {
      if (c == null) return this;
      if (this == NONE || c == NONE) return NONE;
      if (optional() || c.optional()) {
        return many() || c.many() ? ANY_NUMBER : OPTIONAL;
      }
      else {
        return many() || c.many() ? AT_LEAST_ONE : REQUIRED;
      }
    }

    public Cardinality or(Cardinality c) {
      if (c == null) c = NONE;
      if (this == NONE && c == NONE) return NONE;
      if (this == NONE) return c;
      if (c == NONE) return this;
      return optional() && c.optional() ? ANY_NUMBER : AT_LEAST_ONE;
    }

  }

  public static MultiMap<BnfRule, BnfRule> buildExtendsMap(BnfFile file) {
    MultiMap<BnfRule, BnfRule> ruleExtendsMap = newMultiMap();
    for (BnfRule rule : file.getRules()) {
      if (Rule.isPrivate(rule) || Rule.isExternal(rule)) continue;
      BnfRule superRule = file.getRule(getAttribute(rule, KnownAttribute.EXTENDS));
      if (superRule != null) {
        ruleExtendsMap.putValue(superRule, rule);
      }
      BnfRule target = getSynonymTargetOrSelf(rule);
      if (target != rule) {
        ruleExtendsMap.putValue(target, rule);
      }
    }
    for (int i = 0, len = ruleExtendsMap.size(); i < len; i++) {
      boolean changed = false;
      for (BnfRule superRule : ruleExtendsMap.keySet()) {
        final Collection<BnfRule> rules = ruleExtendsMap.get(superRule);
        for (BnfRule rule : ContainerUtil.newArrayList(rules)) {
          changed |= rules.addAll(ruleExtendsMap.get(rule));
        }
      }
      if (!changed) break;
    }
    for (BnfRule rule : ruleExtendsMap.keySet()) {
      ruleExtendsMap.putValue(rule, rule); // add super to itself
    }
    return ruleExtendsMap;
  }

  private static <K, V> MultiMap<K, V> newMultiMap() {
    return new MultiMap<K, V>() {
      @NotNull
      @Override
      protected Map<K, Collection<V>> createMap() {
        return ContainerUtil.newLinkedHashMap();
      }

      @NotNull
      @Override
      protected Collection<V> createCollection() {
        return ContainerUtil.newLinkedHashSet();
      }
    };
  }

  private static final Key<CachedValue<Map<String, String>>> TOKEN_MAP_KEY = Key.create("TOKEN_MAP_KEY");
  public static Map<String, String> getTokenMap(final BnfFile file) {
    CachedValue<Map<String, String>> value = file.getUserData(TOKEN_MAP_KEY);
    if (value == null) {
      file.putUserData(TOKEN_MAP_KEY, value =
        CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<Map<String, String>>() {
          @Nullable
          @Override
          public Result<Map<String, String>> compute() {
            return new Result<Map<String, String>>(computeTokens(file).asInverseMap(), file);
          }
        }, false));
    }
    return value.getValue();
  }

  // string value to constant name
  public static KnownAttribute.ListValue computeTokens(BnfFile file) {
    return getRootAttribute(file, KnownAttribute.TOKENS);
  }

  private static final Key<CachedValue<RuleGraphHelper>> RULE_GRAPH_HELPER_KEY = Key.create("RULE_GRAPH_HELPER_KEY");
  public static RuleGraphHelper getCached(final BnfFile file) {
    CachedValue<RuleGraphHelper> value = file.getUserData(RULE_GRAPH_HELPER_KEY);
    if (value == null) {
      file.putUserData(RULE_GRAPH_HELPER_KEY, value = CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<RuleGraphHelper>() {
        @Nullable
        @Override
        public Result<RuleGraphHelper> compute() {
          return new Result<RuleGraphHelper>(new RuleGraphHelper(file), file);
        }
      }, false));
    }
    return value.getValue();
  }

  public RuleGraphHelper(BnfFile file) {
    this(file, buildExtendsMap(file));
  }

  public RuleGraphHelper(BnfFile file, MultiMap<BnfRule, BnfRule> ruleExtendsMap) {
    myFile = file;
    myRuleExtendsMap = ruleExtendsMap;

    buildRulesGraph();
    buildContentsMap();
  }

  public MultiMap<BnfRule, BnfRule> getRuleExtendsMap() {
    return myRuleExtendsMap;
  }

  public BnfFile getFile() {
    return myFile;
  }

  private void buildContentsMap() {
    List<BnfRule> rules = topoSort(myFile.getRules(), this);

    Set<PsiElement> visited = newTroveSet();
    for (BnfRule rule : rules) {
      if (myRuleContentsMap.containsKey(rule)) continue;
      Map<PsiElement, Cardinality> map = collectMembers(rule, rule.getExpression(), visited);
      myRuleContentsMap.put(rule, map);
      ParserGenerator.LOG.assertTrue(visited.isEmpty());
    }
  }

  public boolean collapseEachOther(BnfRule r1, BnfRule r2) {
    for (BnfRule superRule : myRuleExtendsMap.keySet()) {
      Collection<BnfRule> set = myRuleExtendsMap.get(superRule);
      if (set.contains(r1) && set.contains(r2)) return true;
    }
    return false;
  }

  private void buildRulesGraph() {
    for (BnfRule rule : myFile.getRules()) {
      BnfExpression expression = rule.getExpression();
      for (PsiElement cur = nextOrParent(expression.getPrevSibling(), expression);
           cur != null;
           cur = nextOrParent(cur, expression) ) {
        boolean checkPredicate = cur instanceof BnfReferenceOrToken || cur instanceof BnfStringLiteralExpression;
        if (!checkPredicate || PsiTreeUtil.getParentOfType(cur, BnfPredicate.class) != null) continue;
        BnfRule r = cur instanceof BnfReferenceOrToken? myFile.getRule(cur.getText()) : null;
        if (r != null) {
          myRulesGraph.putValue(rule, r);
        }
        else {
          myRulesWithTokens.add(rule);
        }
      }
    }
    for (BnfRule rule : myFile.getRules()) {
      if (Rule.isLeft(rule) && !Rule.isPrivate(rule) && !Rule.isInner(rule)) {
        for (BnfRule r : getRulesToTheLeft(rule).keySet()) {
          myRulesGraph.putValue(rule, r);
        }
      }
    }
  }

  public Collection<BnfRule> getExtendsRules(BnfRule rule) {
    return myRuleExtendsMap.get(rule);
  }

  public boolean containsTokens(BnfRule rule) {
    return myRulesWithTokens.contains(rule);
  }

  public Collection<BnfRule> getSubRules(BnfRule rule) {
    return myRulesGraph.get(rule);
  }

  @NotNull
  public Map<PsiElement, Cardinality> getFor(BnfRule rule) {
    Map<PsiElement, Cardinality> map = myRuleContentsMap.get(rule); // null for duplicate
    return map == null ? Collections.<PsiElement, Cardinality>emptyMap() : map;
  }

  Map<PsiElement, Cardinality> collectMembers(BnfRule rule, BnfExpression tree, Set<PsiElement> visited) {
    if (tree instanceof BnfPredicate) return Collections.emptyMap();
    if (tree instanceof BnfLiteralExpression) return psiMap(tree, REQUIRED);

    if (!visited.add(tree)) return psiMap(tree, REQUIRED);
    boolean firstNonTrivial = tree == Rule.firstNotTrivial(rule);

    Map<PsiElement, Cardinality> result;
    if (tree instanceof BnfReferenceOrToken) {
      BnfRule targetRule = myFile.getRule(tree.getText());
      if (targetRule != null) {
        if (Rule.isExternal(targetRule)) {
          result = psiMap(newExternalPsi(targetRule.getName()), REQUIRED);
        }
        else if (Rule.isLeft(targetRule)) {
          if (!Rule.isInner(targetRule) && !Rule.isPrivate(targetRule)) {
            result = psiMap();
            result.put(targetRule, REQUIRED);
            result.put(LEFT_MARKER, REQUIRED);
          }
          else {
            result = Collections.emptyMap();
          }
        }
        else if (Rule.isPrivate(targetRule)) {
          result = getPrivateRuleContent(targetRule, visited);
        }
        else {
          result = psiMap(targetRule, REQUIRED);
        }
      }
      else {
        result = psiMap(tree, REQUIRED);
      }
      if (firstNonTrivial && willCollapse(rule, result) && !Rule.isPrivate(rule)) {
        result = Collections.emptyMap();
      }
    }
    else if (tree instanceof BnfExternalExpression) {
      List<BnfExpression> expressionList = ((BnfExternalExpression)tree).getExpressionList();
      if (expressionList.size() == 1 && Rule.isMeta(rule)) {
        result = psiMap(tree, REQUIRED);
      }
      else {
        BnfExpression ruleRef = expressionList.get(0);
        BnfRule metaRule = myFile.getRule(ruleRef.getText());
        if (metaRule == null) {
          result = psiMap(newExternalPsi("#" + ruleRef.getText()), REQUIRED);
        }
        else if (Rule.isPrivate(metaRule)) {
          result = psiMap();
          Map<PsiElement, Cardinality> metaResults = getPrivateRuleContent(metaRule, visited);
          List<String> params = null;
          for (PsiElement member : metaResults.keySet()) {
            Cardinality cardinality = metaResults.get(member);
            if (!(member instanceof BnfExternalExpression)) {
              result.put(member, cardinality);
            }
            else {
              if (params == null) {
                params = collectExtraArguments(metaRule, metaRule.getExpression());
              }
              int idx = params.indexOf(member.getText());
              if (idx > -1 && idx + 1 < expressionList.size()) {
                Map<PsiElement, Cardinality> argMap = collectMembers(rule, expressionList.get(idx + 1), visited);
                for (PsiElement element : argMap.keySet()) {
                  result.put(element, cardinality.and(argMap.get(element)));
                }
              }
            }
          }
        }
        else {
          result = psiMap(metaRule, REQUIRED);
        }
      }
      if (firstNonTrivial && willCollapse(rule, result) && !Rule.isPrivate(rule)) {
        result = Collections.emptyMap();
      }
    }
    else {
      List<BnfExpression> pinned = ContainerUtil.newArrayList();
      GrammarUtil.processPinnedExpressions(rule, new CommonProcessors.CollectProcessor<BnfExpression>(pinned));
      boolean pinApplied = false;

      IElementType type = getEffectiveType(tree);

      List<Map<PsiElement, Cardinality>> list = new ArrayList<Map<PsiElement, Cardinality>>();
      List<BnfExpression> childExpressions = getChildExpressions(tree);
      for (BnfExpression child : childExpressions) {
        Map<PsiElement, Cardinality> nextMap = collectMembers(rule, child, visited);
        if (pinApplied) {
          nextMap = joinMaps(rule, false, BnfTypes.BNF_OP_OPT, Collections.singletonList(nextMap));
        }
        list.add(nextMap);
        if (!pinApplied && pinned.contains(child)) {
          pinApplied = true;
        }
      }
      result = joinMaps(rule, firstNonTrivial, type, list);
      result = result.remove(rule.getExpression()) != null ? joinMaps(rule, false, type, Arrays.asList(result, result)) : result;
    }
    if (rule.getExpression() == tree && Rule.isLeft(rule) && !Rule.isPrivate(rule) && !Rule.isInner(rule)) {
      List<Map<PsiElement, Cardinality>> list = new ArrayList<Map<PsiElement, Cardinality>>();
      Map<BnfRule, Cardinality> rulesToTheLeft = getRulesToTheLeft(rule);
      for (BnfRule r : rulesToTheLeft.keySet()) {
        Cardinality cardinality = rulesToTheLeft.get(r);
        Map<PsiElement, Cardinality> leftMap = psiMap(r, REQUIRED);
        if (cardinality.many()) {
          list.add(joinMaps(rule, false, BnfTypes.BNF_CHOICE, Arrays.asList(leftMap, psiMap(rule, REQUIRED))));
        }
        else {
          list.add(leftMap);
        }
      }
      Map<PsiElement, Cardinality> combinedLeftMap = joinMaps(rule, false, BnfTypes.BNF_CHOICE, list);
      result = joinMaps(rule, false, BnfTypes.BNF_SEQUENCE, Arrays.asList(result, combinedLeftMap));
    }
    visited.remove(tree);
    return result;
  }

  private Map<PsiElement, Cardinality> getPrivateRuleContent(@NotNull BnfRule privateRule, @NotNull Set<PsiElement> visited) {
    Map<PsiElement, Cardinality> result;
    result = myRuleContentsMap.get(privateRule);
    if (result != null) return result;
    Map<PsiElement, Cardinality> map = collectMembers(privateRule, privateRule.getExpression(), visited);
    myRuleContentsMap.put(privateRule, map);
    return map;
  }

  private static Map<BnfRule, Cardinality> getRulesToTheLeft(BnfRule rule) {
    Map<BnfRule, Cardinality> result = ContainerUtil.newLinkedHashMap();
    Map<BnfExpression, BnfExpression> nextMap = new BnfFirstNextAnalyzer().setBackward(true).setPublicRuleOpaque(true).calcNext(rule);
    BnfFile containingFile = (BnfFile)rule.getContainingFile();
    for (BnfExpression e : nextMap.keySet()) {
      if (!(e instanceof BnfReferenceOrToken)) continue;
      BnfRule r = containingFile.getRule(e.getText());
      if (r == null || ParserGeneratorUtil.Rule.isPrivate(r)) continue;
      BnfExpression context = nextMap.get(e);
      Cardinality cardinality = REQUIRED;
      for (PsiElement cur = context; !(cur instanceof BnfRule); cur = cur.getParent()) {
        if (PsiTreeUtil.isAncestor(cur, e, true)) break;
        IElementType curType = getEffectiveType(cur);
        if (curType == BnfTypes.BNF_OP_OPT || curType == BnfTypes.BNF_OP_ONEMORE || curType == BnfTypes.BNF_OP_ZEROMORE) {
          cardinality = cardinality.and(Cardinality.fromNodeType(curType));
        }
      }
      Cardinality prev = result.get(r);
      result.put(r, prev == null? cardinality : cardinality.or(prev));
    }
    return result;
  }

  private Map<PsiElement, Cardinality> joinMaps(@NotNull BnfRule rule, boolean tryCollapse, IElementType type, List<Map<PsiElement, Cardinality>> list) {
    if (list.isEmpty()) return Collections.emptyMap();
    if (type == BnfTypes.BNF_OP_OPT || type == BnfTypes.BNF_OP_ZEROMORE || type == BnfTypes.BNF_OP_ONEMORE) {
      ParserGenerator.LOG.assertTrue(list.size() == 1);
      list = compactInheritors(rule, list);
      Map<PsiElement, Cardinality> m = list.get(0);
      if (tryCollapse && willCollapse(rule, m) && type == BnfTypes.BNF_OP_OPT) {
        return Collections.emptyMap();
      }
      Map<PsiElement, Cardinality> map = psiMap();
      boolean leftMarker = m.containsKey(LEFT_MARKER);
      for (PsiElement t : m.keySet()) {
        Cardinality joinedCard = fromNodeType(type).and(m.get(t));
        if (leftMarker) {
          joinedCard = joinedCard.single();
        }
        map.put(t, joinedCard);
      }
      return map;
    }
    else if (type == BnfTypes.BNF_SEQUENCE || type == BnfTypes.BNF_EXPRESSION || type == BnfTypes.BNF_REFERENCE_OR_TOKEN) {
      list = ContainerUtil.newArrayList(compactInheritors(rule, list));
      for (Iterator<Map<PsiElement, Cardinality>> it = list.iterator(); it.hasNext(); ) {
        if (it.next().isEmpty()) it.remove();
      }
      Map<PsiElement, Cardinality> map = psiMap();
      for (Map<PsiElement, Cardinality> m : list) {
        Cardinality leftMarker = m.get(LEFT_MARKER);
        if (leftMarker == REQUIRED) {
          map.clear();
          leftMarker = null;
        }
        else if (leftMarker == OPTIONAL) {
          for (PsiElement t : map.keySet()) {
            if (!m.containsKey(t)) {
              map.put(t, map.get(t).and(Cardinality.OPTIONAL));
            }
          }
        }
        for (PsiElement t : m.keySet()) {
          if (t == LEFT_MARKER && m != list.get(0)) continue;
          Cardinality c1 = map.get(t);
          Cardinality c2 = m.get(t);
          Cardinality joinedCard;
          if (leftMarker == null) {
            joinedCard = c2.or(c1);

          }
          // handle left semantic in a choice-like way
          else if (c1 == null) {
            joinedCard = c2;
          }
          else {
            if (c1 == REQUIRED) joinedCard = c2.many()? AT_LEAST_ONE : REQUIRED;
            else if (c1 == AT_LEAST_ONE) joinedCard = ANY_NUMBER;
            else joinedCard = c1;
          }
          map.put(t, joinedCard);
        }
      }
      if (tryCollapse && willCollapse(rule, map)) {
        return Collections.emptyMap();
      }
      return map;
    }
    else if (type == BnfTypes.BNF_CHOICE) {
      Map<PsiElement, Cardinality> map = psiMap();
      list = compactInheritors(rule, list);
      for (int i = 0, newListSize = list.size(); i < newListSize; i++) {
        Map<PsiElement, Cardinality> m = list.get(i);
        if (tryCollapse && willCollapse(rule, m)) {
          list.set(i, Collections.<PsiElement, Cardinality>emptyMap());
        }
      }
      Map<PsiElement, Cardinality> m0 = list.get(0);
      map.putAll(m0);
      for (Map<PsiElement, Cardinality> m : list) {
        map.keySet().retainAll(m.keySet());
      }
      for (PsiElement t : new ArrayList<PsiElement>(map.keySet())) {
        map.put(t, REQUIRED.and(m0.get(t)));
        for (Map<PsiElement, Cardinality> m : list) {
          if (m == list.get(0)) continue;
          map.put(t, map.get(t).and(m.get(t)));
        }
      }
      for (Map<PsiElement, Cardinality> m : list) {
        if (tryCollapse && willCollapse(rule, m)) continue;
        for (PsiElement t : m.keySet()) {
          if (map.containsKey(t)) continue;
          map.put(t, OPTIONAL.and(m.get(t)));
        }
      }
      return map;
    }
    else {
      throw new AssertionError("unexpected: " + type);
    }
  }

  private boolean collapseNode(BnfRule rule, PsiElement t) {
    if (myRulesCollapseMap.get(rule).contains(t)) return true;
    if (rule == t || t instanceof BnfRule && collapseEachOther(rule, (BnfRule)t)) {
      myRulesCollapseMap.putValue(rule, t);
      return true;
    }
    else if (isExternalPsi(t) && myRuleExtendsMap.containsScalarValue(rule)) {
      myRulesCollapseMap.putValue(rule, rule);
      return true;
    }
    return false;
  }

  private static <V> Map<PsiElement, V> psiMap(PsiElement k, V v) {
    Map<PsiElement, V> map = new THashMap<PsiElement, V>(1, 1, CARDINALITY_HASHING_STRATEGY);
    map.put(k, v);
    return map;
  }

  private static <V> Map<PsiElement, V> psiMap(Map<PsiElement, V> map) {
    return new THashMap<PsiElement, V>(map, CARDINALITY_HASHING_STRATEGY);
  }

  private static <V> Map<PsiElement, V> psiMap() {
    return new THashMap<PsiElement, V>(3, CARDINALITY_HASHING_STRATEGY);
  }

  /** @noinspection UnusedParameters*/
  private List<Map<PsiElement, Cardinality>> compactInheritors(@Nullable BnfRule forRule, @NotNull List<Map<PsiElement, Cardinality>> mapList) {
    Map<BnfRule, BnfRule> rulesAndAlts = ContainerUtil.newLinkedHashMap();
    for (Map<PsiElement, Cardinality> map : mapList) {
      for (BnfRule rule : ContainerUtil.findAll(map.keySet(), BnfRule.class)) {
        rulesAndAlts.put(rule, rule);
      }
    }
    //if (forRule != null && "".equals(forRule.getName())) {
    //  int gotcha = 1;
    //}

    boolean hasSynonyms = collectSynonymsAndCollapseAlternatives(rulesAndAlts);
    if (rulesAndAlts.size() < 2) {
      return !hasSynonyms ? mapList : replaceRulesInMaps(mapList, rulesAndAlts);
    }
    Set<BnfRule> allRules = ContainerUtil.newLinkedHashSet(ContainerUtil.concat(rulesAndAlts.keySet(), rulesAndAlts.values()));

    List<Map.Entry<BnfRule, Collection<BnfRule>>> applicableSupers = ContainerUtil.newArrayList();
    for (Map.Entry<BnfRule, Collection<BnfRule>> e : myRuleExtendsMap.entrySet()) {
      int count = 0;
      for (BnfRule rule : allRules) {
        if (e.getValue().contains(rule)) count ++;
      }
      if (count > 1) {
        applicableSupers.add(e);
      }
    }
    if (applicableSupers.isEmpty()) {
      return !hasSynonyms ? mapList : replaceRulesInMaps(mapList, rulesAndAlts);
    }

    findTheBestReplacement(rulesAndAlts, applicableSupers);

    return replaceRulesInMaps(mapList, rulesAndAlts);
  }

  private boolean collectSynonymsAndCollapseAlternatives(Map<BnfRule, BnfRule> rulesAndAlts) {
    boolean hasSynonyms = false;
    for (Map.Entry<BnfRule, BnfRule> e : ContainerUtil.newArrayList(rulesAndAlts.entrySet())) {
      BnfRule rule = e.getKey();
      e.setValue(getSynonymTargetOrSelf(rule));
      hasSynonyms |= rule != e.getValue();
      if (myRulesCollapseMap.containsKey(rule)) {
        for (PsiElement r : myRulesCollapseMap.get(rule)) {
          if (r instanceof BnfRule && !rulesAndAlts.containsKey(r)) {
            rulesAndAlts.put((BnfRule)r, (BnfRule)r);
          }
        }
      }
    }
    return hasSynonyms;
  }

  private boolean willCollapse(BnfRule rule, Map<PsiElement, Cardinality> map) {
    return canCollapse(rule, map) && map.size() == 1 && map.values().contains(REQUIRED);
  }

  private boolean canCollapse(BnfRule rule, Map<PsiElement, Cardinality> map) {
    boolean result = false;
    boolean maybeCollapsed = true;
    PsiElement required = null;
    for (PsiElement t : map.keySet()) {
      if (!map.get(t).optional()) {
        if (required == null) {
          required = t;
          maybeCollapsed = required instanceof BnfRule || isExternalPsi(required);
        }
        else {
          maybeCollapsed = false;
        }
        if (!maybeCollapsed) break;
      }
    }
    if (maybeCollapsed) {
      for (PsiElement t : required != null ? Collections.singleton(required) : map.keySet()) {
        result |= collapseNode(rule, t);
      }
    }
    return result;
  }

  private static void findTheBestReplacement(Map<BnfRule, BnfRule> rulesAndAlts,
                                             List<Map.Entry<BnfRule, Collection<BnfRule>>> supers) {
    BitSet bits = new BitSet(rulesAndAlts.size());
    int minI = -1, minC = -1, minS = -1;
    for (int len = Math.min(16, supers.size()), i = (1 << len) - 1; i > 0; i --) {
      if (minC != -1 && Integer.bitCount(i) > minC) continue;
      int curC = 0, curS = 0;
      bits.set(0, rulesAndAlts.size(), true);
      for (int j = 0, bit = 1; j < len; j ++, bit <<= 1) {
        if ((i & bit) == 0) continue;
        Collection<BnfRule> vals = supers.get(j).getValue();
        curC += 1;
        curS += vals.size();
        if (bits.isEmpty()) continue;

        int k = 0;
        for (Map.Entry<BnfRule, BnfRule> e : rulesAndAlts.entrySet()) {
          if (bits.get(k)) {
            if (vals.contains(e.getKey()) || vals.contains(e.getValue())) {
              bits.set(k, false);
            }
          }
          k ++;
        }
        if (!bits.isEmpty()) {
          curC += bits.cardinality();
          curS += bits.cardinality();
        }
      }
      if (minC == -1 || minC > curC || minC == curC && minS > curS) {
        minC = curC;
        minS = curS;
        minI = i;
      }
    }
    for (Map.Entry<BnfRule, BnfRule> e : rulesAndAlts.entrySet()) {
      for (int len = supers.size(), j = 0, bit = 1; j < len; j++, bit <<= 1) {
        if ((minI & bit) == 0) continue;
        Collection<BnfRule> vals = supers.get(j).getValue();
        if (vals.contains(e.getKey()) || vals.contains(e.getValue())) {
          e.setValue(supers.get(j).getKey());
        }
      }
    }
  }

  private static List<Map<PsiElement, Cardinality>> replaceRulesInMaps(List<Map<PsiElement, Cardinality>> mapList,
                                                                       Map<BnfRule, BnfRule> replacementMap) {
    List<Map<PsiElement, Cardinality>> result = ContainerUtil.newArrayListWithCapacity(mapList.size());
    for (Map<PsiElement, Cardinality> map : mapList) {
      Map<PsiElement, Cardinality> copy = psiMap(map);
      result.add(copy);
      for (Map.Entry<BnfRule, BnfRule> e : replacementMap.entrySet()) {
        Cardinality card = copy.remove(e.getKey());
        if (card == null) continue;
        Cardinality cur = copy.get(e.getValue());
        copy.put(e.getValue(), cur == null ? card : cur.or(card));
      }
    }
    return result;
  }

  public static BnfRule getSynonymTargetOrSelf(BnfRule rule) {
    String attr = getAttribute(rule, KnownAttribute.ELEMENT_TYPE);
    if (attr != null) {
      BnfRule realRule = ((BnfFile)rule.getContainingFile()).getRule(attr);
      if (realRule != null && shouldGeneratePsi(realRule, false)) return realRule;
    }
    return rule;
  }

  public static boolean shouldGeneratePsi(BnfRule rule, boolean psiClasses) {
    BnfFile containingFile = (BnfFile)rule.getContainingFile();
    BnfRule grammarRoot = containingFile.getRules().get(0);
    if (grammarRoot == rule) return false;
    if (Rule.isPrivate(rule) || Rule.isExternal(rule)) return false;
    String elementType = getAttribute(rule, KnownAttribute.ELEMENT_TYPE);
    if (!psiClasses) return elementType == null;
    BnfRule thatRule = containingFile.getRule(elementType);
    return thatRule == null || thatRule == grammarRoot || Rule.isPrivate(thatRule) || Rule.isExternal(thatRule);
  }

  @NotNull
  private PsiElement newExternalPsi(String name) {
    PsiElement e = myExternalElements.get(name);
    if (e == null) {
      myExternalElements.put(name, e = new LeafPsiElement(EXTERNAL_TYPE, name));
    }
    return e;
  }

  private static boolean isExternalPsi(PsiElement t) {
    return t instanceof LeafPsiElement && ((LeafPsiElement)t).getElementType() == EXTERNAL_TYPE;
  }

  public boolean canCollapse(@NotNull BnfRule rule) {
    return myRulesCollapseMap.containsKey(rule);
  }
}
TOP

Related Classes of org.intellij.grammar.generator.RuleGraphHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.