/*
* 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.analysis;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.generator.FakeBnfExpression;
import org.intellij.grammar.generator.ParserGeneratorUtil;
import org.intellij.grammar.generator.RuleGraphHelper;
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.*;
/**
* @author gregsh
*/
public class BnfFirstNextAnalyzer {
private static final Logger LOG = Logger.getInstance("org.intellij.grammar.analysis.BnfFirstNextAnalyzer");
public static final String MATCHES_EOF = "-eof-";
public static final String MATCHES_NOTHING = "-never-matches-";
public static final String MATCHES_ANY = "-any-";
public static final BnfExpression BNF_MATCHES_EOF = new FakeBnfExpression(MATCHES_EOF);
public static final BnfExpression BNF_MATCHES_NOTHING = new FakeBnfExpression(MATCHES_NOTHING);
public static final BnfExpression BNF_MATCHES_ANY = new FakeBnfExpression(MATCHES_ANY);
private boolean myBackward;
private boolean myPublicRuleOpaque;
private boolean myNoParent;
private boolean myPredicateLookAhead;
public BnfFirstNextAnalyzer setBackward(boolean backward) {
myBackward = backward;
return this;
}
public BnfFirstNextAnalyzer setPublicRuleOpaque(boolean publicRuleOpaque) {
myPublicRuleOpaque = publicRuleOpaque;
return this;
}
public BnfFirstNextAnalyzer setNoParent(boolean noParent) {
myNoParent = noParent;
return this;
}
public BnfFirstNextAnalyzer setPredicateLookAhead(boolean predicateLookAhead) {
myPredicateLookAhead = predicateLookAhead;
return this;
}
public Set<BnfExpression> calcFirst(@NotNull BnfRule rule) {
Set<BnfExpression> visited = new THashSet<>();
BnfExpression expression = rule.getExpression();
visited.add(expression);
return calcFirstInner(expression, new THashSet<>(), visited);
}
public Map<BnfExpression, BnfExpression> calcNext(@NotNull BnfRule targetRule) {
return calcNextInner(targetRule.getExpression(), new THashMap<>(), new THashSet<>());
}
public Map<BnfExpression, BnfExpression> calcNext(@NotNull BnfExpression targetExpression) {
return calcNextInner(targetExpression, new THashMap<>(), new THashSet<>());
}
private Map<BnfExpression, BnfExpression> calcNextInner(@NotNull BnfExpression targetExpression, Map<BnfExpression, BnfExpression> result, Set<BnfExpression> visited) {
LinkedList<BnfExpression> stack = new LinkedList<>();
THashSet<BnfRule> totalVisited = new THashSet<>();
Set<BnfExpression> curResult = new THashSet<>();
stack.add(targetExpression);
main: while (!stack.isEmpty()) {
PsiElement cur = stack.removeLast();
BnfExpression startingExpr = cur instanceof BnfReferenceOrToken? (BnfExpression)cur : null;
PsiElement parent = cur.getParent();
while (parent instanceof BnfExpression) {
curResult.clear();
PsiElement grandPa = parent.getParent();
if (grandPa instanceof BnfRule && ParserGeneratorUtil.Rule.isExternal((BnfRule)grandPa) ||
grandPa instanceof BnfExternalExpression /*todo support meta rules*/) {
result.put(BNF_MATCHES_ANY, startingExpr);
break;
}
else if (parent instanceof BnfSequence) {
List<BnfExpression> children = ((BnfSequence)parent).getExpressionList();
int idx = children.indexOf(cur);
List<BnfExpression> sublist = myBackward? children.subList(0, idx) : children.subList(idx + 1, children.size());
calcSequenceFirstInner(sublist, curResult, visited);
boolean skipResolve = !curResult.contains(BNF_MATCHES_EOF);
for (BnfExpression e : curResult) {
result.put(e, startingExpr);
}
if (skipResolve) continue main;
}
else if (parent instanceof BnfQuantified) {
IElementType effectiveType = ParserGeneratorUtil.getEffectiveType(parent);
if (effectiveType == BnfTypes.BNF_OP_ZEROMORE || effectiveType == BnfTypes.BNF_OP_ONEMORE) {
calcFirstInner((BnfExpression)parent, curResult, visited);
for (BnfExpression e : curResult) {
result.put(e, startingExpr);
}
}
}
cur = parent;
parent = grandPa;
}
if (!myNoParent && parent instanceof BnfRule && totalVisited.add((BnfRule)parent)) {
BnfRule rule = (BnfRule)parent;
for (PsiReference reference : ReferencesSearch.search(rule, rule.getUseScope()).findAll()) {
PsiElement element = reference.getElement();
if (element instanceof BnfExpression && PsiTreeUtil.getParentOfType(element, BnfPredicate.class) == null) {
BnfAttr attr = PsiTreeUtil.getParentOfType(element, BnfAttr.class);
if (attr != null) {
if (KnownAttribute.getCompatibleAttribute(attr.getName()) == KnownAttribute.RECOVER_WHILE) {
result.put(BNF_MATCHES_ANY, startingExpr);
}
}
else {
stack.add((BnfExpression)element);
}
}
}
}
}
if (result.isEmpty()) result.put(BNF_MATCHES_EOF, null);
return result;
}
private Set<BnfExpression> calcSequenceFirstInner(List<BnfExpression> expressions, final Set<BnfExpression> result, final Set<BnfExpression> visited) {
boolean matchesEof = !result.add(BNF_MATCHES_EOF);
boolean pinApplied = false;
Set<BnfExpression> pinned;
if (!myBackward) {
BnfExpression firstItem = ContainerUtil.getFirstItem(expressions);
if (firstItem == null) return result;
BnfRule rule = ParserGeneratorUtil.Rule.of(firstItem);
pinned = new HashSet<>();
GrammarUtil.processPinnedExpressions(rule, new CommonProcessors.CollectProcessor<>(pinned));
if (firstItem.getParent() instanceof BnfSequence) {
for (BnfExpression e : ((BnfSequence)firstItem.getParent()).getExpressionList()) {
if (e == firstItem) break;
pinApplied |= pinned.contains(e);
}
}
}
else pinned = Collections.emptySet();
List<BnfExpression> list = myBackward ? ContainerUtil.reverse(expressions) : expressions;
for (int i = 0, size = list.size(); i < size; i++) {
if (!result.remove(BNF_MATCHES_EOF)) break;
matchesEof |= pinApplied;
BnfExpression e = list.get(i);
calcFirstInner(e, result, visited, i < size - 1 ? Pair.create(pinned.contains(e), list.subList(i + 1, size)) : null);
pinApplied |= pinned.contains(e);
}
// add empty back if was there before
if (matchesEof) result.add(BNF_MATCHES_EOF);
return result;
}
public Set<BnfExpression> calcFirstInner(BnfExpression expression, Set<BnfExpression> result, Set<BnfExpression> visited) {
return calcFirstInner(expression, result, visited, null);
}
public Set<BnfExpression> calcFirstInner(BnfExpression expression, Set<BnfExpression> result, Set<BnfExpression> visited, @Nullable Pair<Boolean, List<BnfExpression>> forcedNext) {
BnfFile file = (BnfFile)expression.getContainingFile();
if (expression instanceof BnfLiteralExpression) {
result.add(expression);
}
else if (expression instanceof BnfReferenceOrToken) {
BnfRule rule = file.getRule(expression.getText());
if (rule != null) {
if (ParserGeneratorUtil.Rule.isExternal(rule)) {
BnfExpression callExpr = ContainerUtil.getFirstItem(GrammarUtil.getExternalRuleExpressions(rule));
if (callExpr instanceof BnfReferenceOrToken && file.getRule(callExpr.getText()) == null) {
result.add(callExpr);
return result;
}
}
BnfExpression ruleExpression = rule.getExpression();
if (myPublicRuleOpaque && !ParserGeneratorUtil.Rule.isPrivate(rule) ||
!visited.add(ruleExpression)) {
if (!(ParserGeneratorUtil.Rule.firstNotTrivial(rule) instanceof BnfPredicate)) {
result.add(expression);
}
}
else {
calcFirstInner(ruleExpression, result, visited, forcedNext);
boolean removed = visited.remove(ruleExpression);
LOG.assertTrue(removed, "path corruption detected: " + ruleExpression.getText());
}
}
else {
result.add(expression);
}
}
else if (expression instanceof BnfParenthesized) {
calcFirstInner(((BnfParenthesized)expression).getExpression(), result, visited, forcedNext);
if (expression instanceof BnfParenOptExpression) {
result.add(BNF_MATCHES_EOF);
}
}
else if (expression instanceof BnfChoice) {
boolean matchesNothing = result.remove(BNF_MATCHES_NOTHING);
boolean matchesSomething = false;
for (BnfExpression child : ((BnfChoice)expression).getExpressionList()) {
calcFirstInner(child, result, visited, forcedNext);
matchesSomething |= !result.remove(BNF_MATCHES_NOTHING);
}
if (!matchesSomething || matchesNothing) result.add(BNF_MATCHES_NOTHING);
}
else if (expression instanceof BnfSequence) {
calcSequenceFirstInner(((BnfSequence)expression).getExpressionList(), result, visited);
}
else if (expression instanceof BnfQuantified) {
calcFirstInner(((BnfQuantified)expression).getExpression(), result, visited, forcedNext);
IElementType effectiveType = ParserGeneratorUtil.getEffectiveType(expression);
if (effectiveType == BnfTypes.BNF_OP_OPT || effectiveType == BnfTypes.BNF_OP_ZEROMORE) {
result.add(BNF_MATCHES_EOF);
}
}
else if (expression instanceof BnfExternalExpression) {
List<BnfExpression> expressionList = ((BnfExternalExpression)expression).getExpressionList();
if (expressionList.size() == 1 && ParserGeneratorUtil.Rule.isMeta(ParserGeneratorUtil.Rule.of(expression))) {
result.add(expression);
}
else {
BnfExpression ruleRef = expressionList.get(0);
Set<BnfExpression> metaResults = calcFirstInner(ruleRef, new LinkedHashSet<>(), visited, forcedNext);
List<String> params = null;
for (BnfExpression e : metaResults) {
if (e instanceof BnfExternalExpression) {
if (params == null) {
BnfRule metaRule = (BnfRule)ruleRef.getReference().resolve();
if (metaRule == null) {
LOG.error("ruleRef:" + ruleRef.getText() +", metaResult:" + metaResults);
continue;
}
params = GrammarUtil.collectExtraArguments(metaRule, metaRule.getExpression());
}
int idx = params.indexOf(e.getText());
if (idx > -1 && idx + 1 < expressionList.size()) {
calcFirstInner(expressionList.get(idx + 1), result, visited, null);
}
}
else {
result.add(e);
}
}
}
}
else if ((myBackward || !myPredicateLookAhead) && expression instanceof BnfPredicate) {
result.add(BNF_MATCHES_EOF);
}
else if (expression instanceof BnfPredicate) {
IElementType elementType = ((BnfPredicate)expression).getPredicateSign().getFirstChild().getNode().getElementType();
BnfExpression predicateExpression = ParserGeneratorUtil.getNonTrivialNode(((BnfPredicate)expression).getExpression());
boolean skip = predicateExpression instanceof BnfSequence &&
((BnfSequence)predicateExpression).getExpressionList().size() > 1; // todo calc min length ?
// take only one token into account which is not exactly correct but better than nothing
Set<BnfExpression> conditions = calcFirstInner(predicateExpression, newExprSet(), visited, null);
Set<BnfExpression> next;
List<BnfExpression> externalCond = Collections.emptyList();
List<BnfExpression> externalNext;
if (!visited.add(predicateExpression)) {
skip = true;
next = Collections.emptySet();
//result.add(BNF_MATCHES_NOTHING);
}
else {
if (forcedNext == null) {
next = calcNextInner(expression, new THashMap<>(), visited).keySet();
}
else {
next = calcSequenceFirstInner(forcedNext.second, newExprSet(), visited);
}
visited.remove(predicateExpression);
externalCond = filterExternalMethods(conditions);
externalNext = filterExternalMethods(next);
if (!skip) skip = !externalNext.isEmpty() || !externalCond.isEmpty();
}
Set<BnfExpression> mixed = newExprSet();
if (elementType == BnfTypes.BNF_OP_AND) {
if (forcedNext != null && forcedNext.first) {
mixed.addAll(conditions);
}
else if (skip) {
mixed.addAll(next);
mixed.addAll(externalCond);
mixed.remove(BNF_MATCHES_EOF);
}
else if (!conditions.contains(BNF_MATCHES_EOF)) {
if (next.contains(BNF_MATCHES_ANY)) {
mixed.addAll(conditions);
}
else {
mixed.addAll(next);
mixed.retainAll(conditions);
if (mixed.isEmpty() && !involvesTextMatching(conditions)) {
mixed.add(BNF_MATCHES_NOTHING);
}
}
}
else {
mixed.addAll(next);
}
}
else {
if (skip) {
mixed.addAll(next);
mixed.addAll(externalCond); // todo shall be actually inverted
mixed.remove(BNF_MATCHES_EOF);
}
else if (!conditions.contains(BNF_MATCHES_EOF)) {
mixed.addAll(next);
mixed.removeAll(conditions);
if (mixed.isEmpty() && !involvesTextMatching(conditions)) {
mixed.add(BNF_MATCHES_NOTHING);
}
}
else {
mixed.add(BNF_MATCHES_NOTHING);
}
}
result.addAll(mixed);
}
return result;
}
private static List<BnfExpression> filterExternalMethods(Set<BnfExpression> set) {
if (set.removeIf(o -> "<<eof>>".equals(o.getText()))) {
set.add(BNF_MATCHES_EOF);
}
return JBIterable.from(set).filter(GrammarUtil::isExternalReference).toList();
}
private static boolean involvesTextMatching(Set<BnfExpression> set) {
for (BnfExpression o : set) {
if (o instanceof BnfStringLiteralExpression &&
!RuleGraphHelper.getTokenTextToNameMap((BnfFile)o.getContainingFile())
.containsKey(ParserGeneratorUtil.getLiteralValue((BnfStringLiteralExpression)o))) {
return true;
}
}
return false;
}
public Set<String> asStrings(Set<BnfExpression> expressions) {
Set<String> result = new TreeSet<>();
for (BnfExpression expression : expressions) {
if (expression instanceof BnfLiteralExpression) {
String text = expression.getText();
result.add(StringUtil.isQuotedString(text) ? '\'' + GrammarUtil.unquote(text) + '\'' : text);
}
else if (GrammarUtil.isExternalReference(expression)) {
result.add("#" + expression.getText());
}
else {
result.add(expression.getText());
}
}
return result;
}
private static Set<BnfExpression> newExprSet() {
return ContainerUtil.newTroveSet(ParserGeneratorUtil.<BnfExpression>textStrategy());
}
}