/*
* 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.lang.ASTNode;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.impl.FakePsiElement;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.generator.ParserGeneratorUtil;
import org.intellij.grammar.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.regex.Pattern;
/**
* @author gregsh
*/
public abstract class BnfStringImpl extends BnfExpressionImpl implements BnfStringLiteralExpression, PsiLanguageInjectionHost {
private static final Key<PsiReference> REF_KEY = Key.create("BNF_REF_KEY");
private static final Map<ElementPattern<? extends PsiElement>, PsiReferenceProvider> ourProviders;
static {
ourProviders = ContainerUtil.newLinkedHashMap();
new BnfStringRefContributor().registerReferenceProviders(new PsiReferenceRegistrar() {
@Override
public <T extends PsiElement> void registerReferenceProvider(@NotNull ElementPattern<T> pattern,
@NotNull PsiReferenceProvider provider,
double priority) {
ourProviders.put(pattern, provider);
}
});
}
@NotNull
public static PsiReference createPatternReference(@NotNull BnfStringImpl e) {
PsiReference ref = e.getUserData(REF_KEY);
if (ref == null) {
e.putUserData(REF_KEY, ref = new MyPatternReference(e));
}
return ref;
}
@NotNull
public static PsiReference createRuleReference(@NotNull BnfStringImpl e) {
PsiReference ref = e.getUserData(REF_KEY);
if (ref == null) {
e.putUserData(REF_KEY, ref = new MyRuleReference(e));
}
return ref;
}
public BnfStringImpl(ASTNode node) {
super(node);
}
@Override
public PsiElement getNumber() {
return null;
}
@NotNull
public PsiReference[] getReferences() {
// performance: do not run injectors
// return PsiReferenceService.getService().getContributedReferences(this);
List<PsiReference> result = ContainerUtil.newSmartList();
for (Map.Entry<ElementPattern<? extends PsiElement>, PsiReferenceProvider> e : ourProviders.entrySet()) {
ProcessingContext context = new ProcessingContext();
if (e.getKey().accepts(this, context)) {
result.addAll(Arrays.asList(e.getValue().getReferencesByElement(this, context)));
}
}
return result.isEmpty() ? PsiReference.EMPTY_ARRAY : ContainerUtil.toArray(result, new PsiReference[result.size()]);
}
@Override
public PsiReference getReference() {
return ArrayUtil.getFirstElement(getReferences());
}
@Override
public boolean isValidHost() {
return true;
}
@Override
public BnfStringImpl updateText(@NotNull final String text) {
final BnfExpression expression = BnfElementFactory.createExpressionFromText(getProject(), text);
assert expression instanceof BnfStringImpl : text + "-->" + expression;
return (BnfStringImpl)this.replace(expression);
}
@NotNull
@Override
public LiteralTextEscaper<? extends PsiLanguageInjectionHost> createLiteralTextEscaper() {
return new BnfStringLiteralEscaper(this);
}
@Nullable
private static Pattern getPattern(BnfLiteralExpression expression) {
return ParserGeneratorUtil.compilePattern(GrammarUtil.unquote(expression.getText()));
}
private static class MyRuleReference extends BnfReferenceImpl<BnfStringImpl> {
MyRuleReference(BnfStringImpl element) {
super(element, null);
}
@Override
public TextRange getRangeInElement() {
return BnfStringManipulator.getStringTokenRange(getElement());
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
BnfStringImpl element = getElement();
PsiElement string = element.getString();
char quote = string.getText().charAt(0);
return string.replace(BnfElementFactory.createLeafFromText(element.getProject(), quote + newElementName + quote));
}
}
private static class MyPatternReference extends PsiPolyVariantReferenceBase<BnfStringImpl> {
private static final ResolveCache.PolyVariantResolver<MyPatternReference> RESOLVER =
(reference, b) -> reference.multiResolveInner();
MyPatternReference(BnfStringImpl element) {
super(element);
}
@Override
public TextRange getRangeInElement() {
return BnfStringManipulator.getStringTokenRange(getElement());
}
@Override
public boolean isReferenceTo(PsiElement element) {
return matchesElement(getElement(), element) && super.isReferenceTo(element);
}
@NotNull
@Override
public ResolveResult[] multiResolve(boolean b) {
return ResolveCache.getInstance(getElement().getProject()).resolveWithCaching(this, RESOLVER, false, b);
}
@NotNull
public ResolveResult[] multiResolveInner() {
final Pattern pattern = getPattern(getElement());
if (pattern == null) return ResolveResult.EMPTY_ARRAY;
final List<PsiElement> result = ContainerUtil.newArrayList();
BnfAttr thisAttr = ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(getElement(), BnfAttr.class));
BnfAttrs thisAttrs = ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(thisAttr, BnfAttrs.class));
BnfRule thisRule = PsiTreeUtil.getParentOfType(thisAttrs, BnfRule.class);
String thisAttrName = thisAttr.getName();
KnownAttribute knownAttribute = KnownAttribute.getAttribute(thisAttrName);
// collect priority patterns
List<Pattern> otherPatterns = new SmartList<>();
if (knownAttribute != null && !(knownAttribute.getDefaultValue() instanceof KnownAttribute.ListValue)) {
for (BnfAttr attr : thisAttrs.getAttrList()) {
if (attr == thisAttr) break;
if (thisAttrName.equals(attr.getName())) {
BnfAttrPattern attrPattern = attr.getAttrPattern();
BnfLiteralExpression expression = attrPattern != null ? attrPattern.getLiteralExpression() : null;
Pattern p = expression == null ? null : getPattern(expression);
if (p != null) otherPatterns.add(p);
}
}
}
BnfFile file = (BnfFile)thisAttrs.getContainingFile();
int thisOffset = (thisRule != null ? thisRule : thisAttrs).getTextRange().getStartOffset();
List<BnfRule> rules = thisRule != null ? Collections.singletonList(thisRule) : file.getRules();
main:
for (BnfRule rule : rules) {
if (rule.getTextRange().getStartOffset() < thisOffset) continue;
String ruleName = rule.getName();
if (pattern.matcher(ruleName).matches()) {
for (Pattern otherPattern : otherPatterns) {
if (otherPattern.matcher(ruleName).matches()) continue main;
}
result.add(rule);
}
}
if (knownAttribute == KnownAttribute.PIN) {
Set<String> visited = ContainerUtilRt.newHashSet();
for (Object o : thisRule != null ? rules : ContainerUtil.newArrayList(result)) {
BnfRule rule = (BnfRule)o;
GrammarUtil.processExpressionNames(rule, ParserGeneratorUtil.getFuncName(rule), rule.getExpression(), (funcName, expression) -> {
if (!(expression instanceof BnfSequence)) return true;
if (!visited.add(funcName)) return true;
PsiElement firstNotTrivial = ParserGeneratorUtil.Rule.firstNotTrivial(ParserGeneratorUtil.Rule.of(expression));
if (firstNotTrivial == expression) return true;
if (pattern.matcher(funcName).matches()) {
result.add(new MyFakePsiElement(funcName, expression));
}
return true;
});
}
}
return PsiElementResolveResult.createResults(result);
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
// do not rename pattern
return myElement;
}
@NotNull
@Override
public Object[] getVariants() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
}
public static boolean matchesElement(@Nullable BnfLiteralExpression e1, @NotNull PsiElement e2) {
if (e1 == null) return false;
if (e2 instanceof PsiNamedElement) {
String name = ((PsiNamedElement)e2).getName();
Pattern pattern = getPattern(e1);
if (name == null || pattern == null || !pattern.matcher(name).matches()) {
return false;
}
}
return true;
}
private static class MyFakePsiElement extends FakePsiElement implements BnfCompositeElement {
private final String myFuncName;
private final BnfExpression myExpression;
MyFakePsiElement(String funcName, BnfExpression expression) {
myFuncName = funcName;
myExpression = expression;
}
@Override
public String getName() {
return myFuncName;
}
@NotNull
@Override
public PsiElement getNavigationElement() {
return myExpression;
}
@Override
public TextRange getTextRange() {
return myExpression.getTextRange();
}
@Override
public PsiElement getParent() {
return myExpression.getParent();
}
@Override
public <R> R accept(@NotNull BnfVisitor<R> visitor) {
return null;
}
}
}