/* * Copyright 2011-present Greg 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.SyntaxTraverser; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; import com.intellij.util.CommonProcessors; import com.intellij.util.ObjectUtils; 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 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.isDoubleAngles; /** * @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 IElementType EXTERNAL_TYPE = new FakeElementType("EXTERNAL_TYPE", Language.ANY); private static final IElementType MARKER_TYPE = new FakeElementType("MARKER_TYPE", Language.ANY); private static final PsiElement LEFT_MARKER = new FakeBnfExpression(MARKER_TYPE, "LEFT_MARKER"); private static final PsiElement NOT_EMPTY_MARKER = new FakeBnfExpression(MARKER_TYPE, "NOT_EMPTY_MARKER"); private static final Object RECURSION_MARKER = "RECURSION_DETECTED"; 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 (isPrivateOrNoType(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); if (superRule != null) { ruleExtendsMap.putValue(superRule, target); } } } 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(); } }; } @NotNull public static Map<String, String> getTokenNameToTextMap(final BnfFile file) { return CachedValuesManager.getCachedValue(file, () -> new CachedValueProvider.Result<>(computeTokens(file).asMap(), file)); } @NotNull public static Map<String, String> getTokenTextToNameMap(final BnfFile file) { return CachedValuesManager.getCachedValue(file, () -> new CachedValueProvider.Result<>(computeTokens(file).asInverseMap(), file)); } // 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.Result<>(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(); buildCollapseMap(); buildContentsMap(); } public MultiMap<BnfRule, BnfRule> getRuleExtendsMap() { return myRuleExtendsMap; } public BnfFile getFile() { return myFile; } public boolean canCollapse(@NotNull BnfRule rule) { return myRulesCollapseMap.containsKey(rule); } private void buildCollapseMap() { BnfFirstNextAnalyzer analyzer = new BnfFirstNextAnalyzer() .setPublicRuleOpaque(true).setNoParent(true).setPredicateLookAhead(true); for (BnfRule rule : myFile.getRules()) { if (!myRuleExtendsMap.containsScalarValue(rule)) continue; Set<BnfExpression> first = analyzer.calcFirst(rule); for (BnfExpression expression : first) { BnfRule r = expression instanceof BnfReferenceOrToken ? myFile.getRule(expression.getText()) : null; BnfRule commonSuper = r != null ? getCommonSuperRule(rule, r) : null; if (expression != BnfFirstNextAnalyzer.BNF_MATCHES_ANY && commonSuper == null) continue; Map<BnfExpression, BnfExpression> map = analyzer.calcNext(expression); if (!map.containsKey(BnfFirstNextAnalyzer.BNF_MATCHES_EOF)) continue; if (commonSuper != null) { myRulesCollapseMap.putValue(rule, commonSuper); } myRulesCollapseMap.putValue(rule, ObjectUtils.notNull(r, rule)); } if (myRulesCollapseMap.containsKey(rule)) { myRulesCollapseMap.putValue(rule, rule); } } } private void buildContentsMap() { List<BnfRule> rules = topoSort(myFile.getRules(), this); Set<Object> visited = ContainerUtil.newLinkedHashSet(); for (BnfRule rule : rules) { collectMembers(rule, visited); visited.clear(); } } private Map<PsiElement, Cardinality> collectMembers(@NotNull BnfRule rule, Set<Object> visited) { Map<PsiElement, Cardinality> result = myRuleContentsMap.get(rule); if (result != null) return result; if (Rule.isExternal(rule)) { result = psiMap(newExternalPsi(rule.getName()), REQUIRED); } else { result = collectMembers(rule, rule.getExpression(), visited); } if (visited.size() > 1 && visited.contains(RECURSION_MARKER) && isPrivateOrNoType(rule)) { return result; } result.remove(NOT_EMPTY_MARKER); // todo private rules should retain this myRuleContentsMap.put(rule, result); return result; } @Nullable private BnfRule getCommonSuperRule(BnfRule r1, BnfRule r2) { int count = Integer.MAX_VALUE; BnfRule result = null; for (BnfRule superRule : myRuleExtendsMap.keySet()) { Collection<BnfRule> set = myRuleExtendsMap.get(superRule); if (set.contains(r1) && set.contains(r2)) { if (count > set.size()) { count = set.size(); result = superRule; } } } return result; } private void buildRulesGraph() { SyntaxTraverser<PsiElement> s = SyntaxTraverser.psiTraverser() .expand(o -> !(o instanceof BnfPredicate || o instanceof BnfExternalExpression)); for (BnfRule rule : myFile.getRules()) { for (PsiElement e : s.withRoot(rule.getExpression()).filter(BnfExpression.class)) { BnfReferenceOrToken ruleRef = e instanceof BnfReferenceOrToken ? (BnfReferenceOrToken)e : e instanceof BnfExternalExpression ? PsiTreeUtil.findChildOfType(e, BnfReferenceOrToken.class) : null; BnfRule r = ruleRef != null ? ruleRef.resolveRule() : null; if (r != null) { myRulesGraph.putValue(rule, r); } else if (e instanceof BnfReferenceOrToken || e instanceof BnfStringLiteralExpression) { myRulesWithTokens.add(rule); } } } for (BnfRule rule : myFile.getRules()) { if (Rule.isLeft(rule) && !isPrivateOrNoType(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.emptyMap() : map; } Map<PsiElement, Cardinality> collectMembers(BnfRule rule, BnfExpression tree, Set<Object> visited) { if (tree instanceof BnfPredicate) return Collections.emptyMap(); if (tree instanceof BnfLiteralExpression) return psiMap(tree, REQUIRED); if (!visited.add(tree)) { visited.add(RECURSION_MARKER); return psiMap(tree, REQUIRED); } try { return collectMembersInner(rule, tree, visited); } finally { visited.remove(tree); } } private Map<PsiElement, Cardinality> collectMembersInner(BnfRule rule, BnfExpression tree, Set<Object> visited) { boolean firstNonTrivial = tree == Rule.firstNotTrivial(rule); boolean outerLeft = (firstNonTrivial || rule.getExpression() == tree) && Rule.isLeft(rule) && !isPrivateOrNoType(rule) && !Rule.isInner(rule); boolean tryCollapse = firstNonTrivial && !outerLeft && !isPrivateOrNoType(rule) && !Rule.isFake(rule); Map<PsiElement, Cardinality> result; if (tree instanceof BnfReferenceOrToken) { BnfRule targetRule = ((BnfReferenceOrToken)tree).resolveRule(); if (targetRule != null) { if (Rule.isExternal(targetRule)) { result = psiMap(newExternalPsi(targetRule.getName()), REQUIRED); } else if (Rule.isLeft(targetRule)) { if (!Rule.isInner(targetRule) && !isPrivateOrNoType(targetRule)) { result = psiMap(); result.put(getSynonymTargetOrSelf(targetRule), REQUIRED); result.put(LEFT_MARKER, REQUIRED); } else { result = Collections.emptyMap(); } } else if (isPrivateOrNoType(targetRule)) { result = collectMembers(targetRule, visited); } else { result = psiMap(getSynonymTargetOrSelf(targetRule), REQUIRED); } } else { result = psiMap(tree, REQUIRED); } if (tryCollapse && willCollapse(rule, result)) { result = Collections.emptyMap(); } } else if (tree instanceof BnfExternalExpression) { List<BnfExpression> expressionList = ((BnfExternalExpression)tree).getExpressionList(); if (expressionList.size() == 1 && Rule.isMeta(rule)) { result = psiMap(newExternalPsi(tree.getText()), REQUIRED); } else { BnfExpression ruleRef = expressionList.get(0); BnfRule metaRule = ((BnfReferenceOrToken)ruleRef).resolveRule(); if (metaRule == null) { result = psiMap(newExternalPsi("#" + ruleRef.getText()), REQUIRED); } else if (isPrivateOrNoType(metaRule)) { result = psiMap(); Map<PsiElement, Cardinality> metaResults = collectMembers(metaRule, visited); List<String> params = null; for (PsiElement member : metaResults.keySet()) { Cardinality cardinality = metaResults.get(member); if (!isExternalPsi(member)) { 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 (tryCollapse && willCollapse(rule, result)) { result = Collections.emptyMap(); } } else { List<BnfExpression> pinned = ContainerUtil.newArrayList(); GrammarUtil.processPinnedExpressions(rule, new CommonProcessors.CollectProcessor<>(pinned)); boolean pinApplied = false; IElementType type = getEffectiveType(tree); List<Map<PsiElement, Cardinality>> list = ContainerUtil.newArrayList(); 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, tryCollapse, type, list); result = type == BnfTypes.BNF_SEQUENCE && visited.contains(RECURSION_MARKER) && result.remove(rule.getExpression()) != null ? joinMaps(rule, false, type, Arrays.asList(result, result)) : result; } if (outerLeft && rule.getExpression() == tree) { List<Map<PsiElement, Cardinality>> list = ContainerUtil.newArrayList(); 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, true, BnfTypes.BNF_SEQUENCE, Arrays.asList(result, combinedLeftMap)); } return result; } 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); for (BnfExpression e : nextMap.keySet()) { if (!(e instanceof BnfReferenceOrToken)) continue; BnfRule r = ((BnfReferenceOrToken)e).resolveRule(); if (r == null || isPrivateOrNoType(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); if (tryCollapse) { for (int i = 0, newListSize = list.size(); i < newListSize; i++) { Map<PsiElement, Cardinality> m = list.get(i); if (willCollapse(rule, m)) { list.set(i, Collections.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<>(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))); } } boolean notEmpty = true; empty: for (Map<PsiElement, Cardinality> m : list) { for (Cardinality c : m.values()) { if (!c.optional()) continue empty; } notEmpty = false; } if (notEmpty) map.put(NOT_EMPTY_MARKER, REQUIRED); return map; } else { throw new AssertionError("unexpected: " + type); } } private boolean canCollapseBy(BnfRule rule, PsiElement t) { if (myRulesCollapseMap.get(rule).contains(t)) return true; if (rule == t || t instanceof BnfRule && getCommonSuperRule(rule, (BnfRule)t) != null) { 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<>(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<>(map, CARDINALITY_HASHING_STRATEGY); } private static <V> Map<PsiElement, V> psiMap() { return new THashMap<>(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(); Map<PsiElement, BnfRule> externalMap = ContainerUtil.newLinkedHashMap(); for (Map<PsiElement, Cardinality> map : mapList) { for (PsiElement element : map.keySet()) { BnfRule r = null; if (element instanceof BnfRule) r = (BnfRule)element; else if (isExternalPsi(element) && !element.getText().startsWith("#") && !isDoubleAngles(element.getText())) { String text = element.getText(); BnfRule rule = myFile.getRule(text); if (Rule.isExternal(rule)) { r = myFile.getRule(getAttribute(rule, KnownAttribute.EXTENDS)); if (r != null) externalMap.put(element, r); } } if (r != null) rulesAndAlts.put(r, r); } } //if (forRule != null && "".equals(forRule.getName())) { // int gotcha = 1; //} boolean hasSynonyms = collectSynonymsAndCollapseAlternatives(rulesAndAlts); if (rulesAndAlts.size() < 2) { return !hasSynonyms ? mapList : replaceRulesInMaps(mapList, rulesAndAlts, externalMap); } 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, externalMap); } findTheBestReplacement(rulesAndAlts, applicableSupers); return replaceRulesInMaps(mapList, rulesAndAlts, externalMap); } 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(); 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) { if (!canCollapse(rule, map)) return false; boolean requiredFound = false; for (PsiElement t : map.keySet()) { if (PsiUtilCore.getElementType(t) == MARKER_TYPE) continue; if (requiredFound || map.get(t) != REQUIRED) return false; if (isExternalPsi(t)) return false; requiredFound = true; } return requiredFound; } private boolean canCollapse(BnfRule rule, Map<PsiElement, Cardinality> map) { boolean result = false; boolean maybeCollapsed = true; PsiElement required = null; for (PsiElement t : map.keySet()) { if (PsiUtilCore.getElementType(t) == MARKER_TYPE) continue; 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 |= canCollapseBy(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, Map<PsiElement, BnfRule> externalMap) { 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 (PsiElement element : map.keySet()) { BnfRule rule = isExternalPsi(element) ? externalMap.get(element) : null; BnfRule replacement = rule != null ? replacementMap.get(rule) : null; if (replacement != null) { copy.put(rule, copy.remove(element)); } } 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 hasPsiClass(BnfRule rule) { return shouldGeneratePsi(rule, true); } public static boolean hasElementType(BnfRule rule) { return shouldGeneratePsi(rule, false); } public static boolean isPrivateOrNoType(BnfRule rule) { return Rule.isPrivate(rule) || "".equals(getAttribute(rule, KnownAttribute.ELEMENT_TYPE)); } private 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 attr = getAttribute(rule, KnownAttribute.ELEMENT_TYPE); if (!psiClasses) return !"".equals(attr); BnfRule thatRule = containingFile.getRule(attr); 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 FakeBnfExpression(EXTERNAL_TYPE, name)); } return e; } private static boolean isExternalPsi(PsiElement t) { return t instanceof LeafPsiElement && ((LeafPsiElement)t).getElementType() == EXTERNAL_TYPE; } }