/* * 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.psi.impl; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.SyntaxTraverser; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.PairProcessor; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import org.intellij.grammar.KnownAttribute; import org.intellij.grammar.generator.ParserGeneratorUtil; import org.intellij.grammar.parser.GeneratedParserUtilBase; import org.intellij.grammar.psi.*; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Comparator; import java.util.List; import static com.intellij.psi.SyntaxTraverser.psiTraverser; import static org.intellij.grammar.generator.ParserGeneratorUtil.*; import static org.intellij.grammar.psi.BnfTypes.BNF_SEQUENCE; /** * @author gregsh */ public class GrammarUtil { public final static Comparator<BnfNamedElement> NAME_COMPARATOR = (o1, o2) -> Comparing.compare(o1.getName(), o2.getName()); public static PsiElement getDummyAwarePrevSibling(PsiElement child) { PsiElement prevSibling = child.getPrevSibling(); while (prevSibling instanceof GeneratedParserUtilBase.DummyBlock) { prevSibling = prevSibling.getLastChild(); } if (prevSibling != null) return prevSibling; PsiElement parent = child.getParent(); while (parent instanceof GeneratedParserUtilBase.DummyBlock && parent.getPrevSibling() == null) { parent = parent.getParent(); } return parent == null? null : parent.getPrevSibling(); } public static boolean equalsElement(BnfExpression e1, BnfExpression e2) { if (e1 == null) return e2 == null; if (e2 == null) return false; if (ParserGeneratorUtil.getEffectiveType(e1) != ParserGeneratorUtil.getEffectiveType(e2)) return false; if (isOneTokenExpression(e1)) { return e1.getText().equals(e2.getText()); } else { for (PsiElement c1 = e1.getFirstChild(), c2 = e2.getFirstChild(); ;) { boolean f1 = c1 == null || c1 instanceof BnfExpression; boolean f2 = c2 == null || c2 instanceof BnfExpression; if (f1 && f2 && !equalsElement((BnfExpression)c1, (BnfExpression)c2)) return false; if (f1 && f2 || !f1) c1 = c1 == null? null : c1.getNextSibling(); if (f1 && f2 || !f2) c2 = c2 == null? null : c2.getNextSibling(); if (c1 == null && c2 == null) return true; } } } public static boolean isInAttributesReference(@Nullable PsiElement element) { return PsiTreeUtil.getParentOfType(element, BnfRule.class, BnfAttrs.class) instanceof BnfAttrs; } public static boolean isOneTokenExpression(@Nullable BnfExpression e1) { return e1 instanceof BnfLiteralExpression || e1 instanceof BnfReferenceOrToken; } public static boolean isExternalReference(@Nullable PsiElement psiElement) { PsiElement parent = psiElement == null? null : psiElement.getParent(); if (parent instanceof BnfExternalExpression && ((BnfExternalExpression)parent).getExpressionList().get(0) == psiElement) return true; if (parent instanceof BnfSequence && parent.getFirstChild() == psiElement) parent = parent.getParent(); return parent instanceof BnfRule && ParserGeneratorUtil.Rule.isExternal((BnfRule)parent); } public static List<BnfExpression> getExternalRuleExpressions(@NotNull BnfRule subRule) { BnfExpression expression = subRule.getExpression(); return expression instanceof BnfSequence ? ((BnfSequence)expression).getExpressionList() : Collections.singletonList(expression); } public static List<String> collectExtraArguments(BnfRule rule, BnfExpression expression) { if (!ParserGeneratorUtil.Rule.isMeta(rule) && !ParserGeneratorUtil.Rule.isExternal(rule)) return Collections.emptyList(); List<String> result = ContainerUtil.newSmartList(); for (BnfExternalExpression o : bnfTraverserNoAttrs(expression).filter(BnfExternalExpression.class)) { List<BnfExpression> list = o.getExpressionList(); if (list.size() == 1) { String text = "<<"+list.get(0).getText() +">>"; if (!result.contains(text)) { result.add(text); } } } if (ParserGeneratorUtil.Rule.isMeta(rule)) { String attr = getAttribute(rule, KnownAttribute.RECOVER_WHILE); if (isDoubleAngles(attr) && !result.contains(attr)) { result.add(attr); } } return result; } @NotNull public static String unquote(@NotNull String str) { return StringUtil.unquoteString(str); } public static boolean isDoubleAngles(@Nullable String str) { return str != null && str.startsWith("<<") && str.endsWith(">>"); } public static boolean processExpressionNames(BnfRule rule, String funcName, BnfExpression expression, PairProcessor<String, BnfExpression> processor) { if (isAtomicExpression(expression)) return true; BnfExpression nonTrivialExpression = expression; for (BnfExpression e = expression, n = getTrivialNodeChild(e); n != null; e = n, n = getTrivialNodeChild(e)) { if (!processor.process(funcName, e)) return false; nonTrivialExpression = n; } final List<BnfExpression> children = getChildExpressions(nonTrivialExpression); for (int i = 0, childExpressionsSize = children.size(); i < childExpressionsSize; i++) { BnfExpression child = children.get(i); if (isAtomicExpression(child)) continue; String nextName = ParserGeneratorUtil.isTokenSequence(rule, child)? funcName : getNextName(funcName, i); if (!processExpressionNames(rule, nextName, child, processor)) return false; } return processor.process(funcName, nonTrivialExpression); } public static boolean processPinnedExpressions(final BnfRule rule, final Processor<BnfExpression> processor) { return processPinnedExpressions(rule, (bnfExpression, pinMatcher) -> processor.process(bnfExpression)); } public static boolean processPinnedExpressions(final BnfRule rule, final PairProcessor<BnfExpression, PinMatcher> processor) { return processExpressionNames(rule, getFuncName(rule), rule.getExpression(), (funcName, expression) -> { if (!(expression instanceof BnfSequence)) return true; List<BnfExpression> children = getChildExpressions(expression); if (children.size() < 2) return true; PinMatcher pinMatcher = new PinMatcher(rule, BNF_SEQUENCE, funcName); boolean pinApplied = false; for (int i = 0, childExpressionsSize = children.size(); i < childExpressionsSize; i++) { BnfExpression child = children.get(i); if (!pinApplied && pinMatcher.matches(i, child)) { pinApplied = true; if (!processor.process(child, pinMatcher)) return false; } } return true; }); } public static boolean isAtomicExpression(BnfExpression tree) { return tree instanceof BnfReferenceOrToken || tree instanceof BnfLiteralExpression || tree instanceof BnfExternalExpression; } public static SyntaxTraverser<PsiElement> bnfTraverser(PsiElement root) { return psiTraverser().withRoot(root). forceDisregardTypes(Conditions.equalTo(GeneratedParserUtilBase.DUMMY_BLOCK)). filter(Conditions.instanceOf(BnfCompositeElement.class)); } public static SyntaxTraverser<PsiElement> bnfTraverserNoAttrs(PsiElement root) { return bnfTraverser(root).forceIgnore(Conditions.instanceOf(BnfAttrs.class)); } public static String getMethodName(BnfRule rule, PsiElement element) { final BnfExpression target = PsiTreeUtil.getParentOfType(element, BnfExpression.class, false); String funcName = getFuncName(rule); if (target == null) return funcName; final Ref<String> ref = Ref.create(null); processExpressionNames(rule, funcName, rule.getExpression(), (funcName1, expression) -> { if (target == expression) { ref.set(funcName1); return false; } return true; }); return ref.get(); } @NotNull public static String getIdText(@Nullable PsiElement id) { return id == null ? "" : stripQuotesAroundId(id.getText()); } @Contract("!null->!null") public static String stripQuotesAroundId(String text) { return isIdQuoted(text) ? text.substring(1, text.length() - 1) : text; } public static boolean isIdQuoted(@Nullable String text) { return text != null && text.startsWith("<") && text.endsWith(">"); } }