/*
* 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;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import org.intellij.grammar.analysis.BnfFirstNextAnalyzer;
import org.intellij.grammar.generator.BnfConstants;
import org.intellij.grammar.generator.ExpressionHelper;
import org.intellij.grammar.generator.ParserGeneratorUtil;
import org.intellij.grammar.generator.RuleGraphHelper;
import org.intellij.grammar.psi.BnfAttr;
import org.intellij.grammar.psi.BnfExpression;
import org.intellij.grammar.psi.BnfFile;
import org.intellij.grammar.psi.BnfRule;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author gregsh
*/
public class BnfDocumentationProvider implements DocumentationProvider {
@Nullable
public String getQuickNavigateInfo(final PsiElement element, PsiElement originalElement) {
return null;
}
@Nullable
public List<String> getUrlFor(final PsiElement element, final PsiElement originalElement) {
return null;
}
@Nullable
public String generateDoc(final PsiElement element, final PsiElement originalElement) {
if (element instanceof BnfRule) {
final BnfRule rule = (BnfRule)element;
BnfFirstNextAnalyzer analyzer = new BnfFirstNextAnalyzer();
Set<String> first = analyzer.asStrings(analyzer.calcFirst(rule));
Set<String> next = analyzer.asStrings(analyzer.calcNext(rule).keySet());
StringBuilder docBuilder = new StringBuilder();
String[] firstS = first.toArray(new String[first.size()]);
Arrays.sort(firstS);
docBuilder.append("<h1>Starts with:</h1>");
docBuilder.append("<code>").append(StringUtil.escapeXml(StringUtil.join(firstS, " | "))).append("</code>");
String[] nextS = next.toArray(new String[next.size()]);
Arrays.sort(nextS);
docBuilder.append("<br><h1>Followed by:</h1>");
docBuilder.append("<code>").append(StringUtil.escapeXml(StringUtil.join(nextS, " | "))).append("</code>");
BnfFile file = (BnfFile)rule.getContainingFile();
String recover = file.findAttributeValue(rule, KnownAttribute.RECOVER_WHILE, null);
if (BnfConstants.RECOVER_AUTO.equals(recover)) {
docBuilder.append("<br><h1>#auto recovery predicate:</h1>");
docBuilder.append("<code>");
docBuilder.append("private ").append(rule.getName()).append("_recover ::= !(");
boolean f = true;
for (String s : nextS) {
if (s.startsWith("-") || s.startsWith("<")) continue;
if (file.getRule(s) != null) continue;
if (f) f = false;
else docBuilder.append(" | ");
docBuilder.append(StringUtil.escapeXml(s));
}
docBuilder.append(")");
docBuilder.append("</code>");
}
dumpPriorityTable(docBuilder, rule, file);
dumpContents(docBuilder, rule, file);
return docBuilder.toString();
}
else if (element instanceof BnfAttr) {
KnownAttribute attribute = KnownAttribute.getAttribute(((BnfAttr)element).getName());
if (attribute != null) return attribute.getDescription();
}
return null;
}
@Nullable
public PsiElement getDocumentationElementForLookupItem(final PsiManager psiManager, final Object obj5ect, final PsiElement element) {
return null;
}
@Nullable
public PsiElement getDocumentationElementForLink(final PsiManager psiManager, final String link, final PsiElement context) {
return null;
}
private static void dumpContents(StringBuilder docBuilder, BnfRule rule, BnfFile file) {
Map<PsiElement, RuleGraphHelper.Cardinality> map = RuleGraphHelper.getCached(file).getFor(rule);
Collection<BnfRule> sortedPublicRules = ParserGeneratorUtil.getSortedPublicRules(map.keySet());
Collection<BnfExpression> sortedTokens = ParserGeneratorUtil.getSortedTokens(map.keySet());
Collection<LeafPsiElement> sortedExternalRules = ParserGeneratorUtil.getSortedExternalRules(map.keySet());
if (sortedPublicRules.isEmpty() && sortedTokens.isEmpty()) {
docBuilder.append("\n<br><h1>Contains no public rules and no tokens</h1>");
}
else {
if (sortedPublicRules.size() > 0) {
printElements(map, sortedPublicRules, docBuilder.append("\n<br><h1>Contains public rules:</h1>"));
}
else {
docBuilder.append("<h2>Contains no public rules</h2>");
}
if (sortedTokens.size() > 0) {
printElements(map, sortedTokens, docBuilder.append("\n<br><h1>Contains tokens:</h1>"));
}
else {
docBuilder.append("<h2>Contains no tokens</h2>");
}
}
if (!sortedExternalRules.isEmpty()) {
printElements(map, sortedExternalRules, docBuilder.append("\n<br><h1>Contains external rules:</h1>"));
}
}
private static void dumpPriorityTable(StringBuilder docBuilder, BnfRule rule, BnfFile file) {
ExpressionHelper.ExpressionInfo expressionInfo = ExpressionHelper.getCached(file).getExpressionInfo(rule);
if (expressionInfo == null) return;
final ExpressionHelper.OperatorInfo ruleOperator = expressionInfo.operatorMap.get(rule);
final int priority = expressionInfo.getPriority(rule);
docBuilder.append("\n<br><h1>Priority table:");
appendColored(docBuilder, String.format(" %s (%s)", ruleOperator != null ? ruleOperator.type : "priority group", priority));
docBuilder.append("</h1>");
expressionInfo.dumpPriorityTable(docBuilder.append("<code><pre>"), (sb, operatorInfo) -> {
if (ruleOperator == operatorInfo ||
ruleOperator == null && Comparing.equal(expressionInfo.getPriority(operatorInfo.rule), priority)) {
appendColored(sb, operatorInfo);
}
else {
sb.append(operatorInfo);
}
}).append("</pre></code>");
}
private static void appendColored(StringBuilder sb, Object o) {
sb.append("<font").append(" color=\"#").append(ColorUtil.toHex(JBColor.BLUE)).append("\">");
sb.append(o);
sb.append("</font>");
}
public static void printElements(Map<PsiElement, RuleGraphHelper.Cardinality> map,
Collection<? extends PsiElement> collection,
StringBuilder sb) {
for (PsiElement r : collection) {
sb.append(" ").append(r instanceof PsiNamedElement? ((PsiNamedElement)r).getName() : r.getText()).
append(RuleGraphHelper.getCardinalityText(map.get(r)));
}
}
}