/*
* Copyright 2011-2014 Gregory 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.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.psi.BnfAttr;
import org.intellij.grammar.psi.BnfRule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.intellij.grammar.generator.ParserGeneratorUtil.*;
import static org.intellij.grammar.psi.BnfTypes.BNF_REFERENCE_OR_TOKEN;
import static org.intellij.grammar.psi.BnfTypes.BNF_STRING;
/**
* @author gregsh
*/
public class RuleMethodsHelper {
private final RuleGraphHelper myGraphHelper;
private final ExpressionHelper myExpressionHelper;
private final Map<String, String> mySimpleTokens;
private boolean myGenerateTokensDef;
private final Map<BnfRule, Pair<Map<String, MethodInfo>, Collection<MethodInfo>>> myMethods;
public RuleMethodsHelper(RuleGraphHelper ruleGraphHelper,
ExpressionHelper expressionHelper,
Map<String, String> simpleTokens,
boolean generateTokensDef) {
myGraphHelper = ruleGraphHelper;
myExpressionHelper = expressionHelper;
mySimpleTokens = Collections.unmodifiableMap(simpleTokens);
myGenerateTokensDef = generateTokensDef;
myMethods = ContainerUtil.newLinkedHashMap();
}
public void buildMaps(Collection<BnfRule> sortedPsiRules) {
Map<String, String> tokensReversed = RuleGraphHelper.computeTokens(myGraphHelper.getFile()).asMap();
for (BnfRule rule : sortedPsiRules) {
calcMethods(rule, tokensReversed);
}
}
@NotNull
public Collection<MethodInfo> getFor(BnfRule rule) {
return myMethods.get(rule).second;
}
@Nullable
public MethodInfo getMethodInfo(BnfRule rule, String name) {
return myMethods.get(rule).first.get(name);
}
protected void calcMethods(BnfRule rule, Map<String, String> tokensReversed) {
List<MethodInfo> result = ContainerUtil.newArrayList();
Map<PsiElement, RuleGraphHelper.Cardinality> cardMap = myGraphHelper.getFor(rule);
for (PsiElement element : cardMap.keySet()) {
RuleGraphHelper.Cardinality c = myExpressionHelper.fixCardinality(rule, element, cardMap.get(element));
String pathName = getRuleOrTokenNameForPsi(element, c);
if (pathName == null) continue;
if (element instanceof BnfRule) {
BnfRule resultType = (BnfRule)element;
if (!ParserGeneratorUtil.Rule.isPrivate(rule)) {
result.add(new MethodInfo(1, pathName, pathName, resultType, c)); // public rule
}
}
else {
result.add(new MethodInfo(2, pathName, pathName, null, c)); // token
}
}
Collections.sort(result);
BnfAttr attr = findAttribute(rule, KnownAttribute.GENERATE_TOKEN_ACCESSORS);
boolean generateTokens = attr == null? myGenerateTokensDef :
Boolean.TRUE.equals(getAttributeValue(attr.getExpression()));
Map<String, MethodInfo> basicMethods = ContainerUtil.newLinkedHashMap();
for (MethodInfo methodInfo : result) {
basicMethods.put(methodInfo.name, methodInfo);
if (methodInfo.type == 2) {
boolean registered = tokensReversed.containsKey(methodInfo.name);
String pattern = tokensReversed.get(methodInfo.name);
// only regexp and lowercase tokens accessors are generated by default
if (!(generateTokens || registered && (pattern == null || ParserGeneratorUtil.isRegexpToken(pattern)))) {
methodInfo.name = ""; // disable token
}
}
}
KnownAttribute.ListValue methods = getAttribute(rule, KnownAttribute.METHODS);
for (Pair<String, String> pair : methods) {
if (StringUtil.isEmpty(pair.first)) continue;
MethodInfo methodInfo = basicMethods.get(pair.first);
if (methodInfo != null) {
methodInfo.name = ""; // suppress or user method override
}
if (StringUtil.isNotEmpty(pair.second)) {
MethodInfo basicInfo = basicMethods.get(pair.second);
if (basicInfo != null && (basicInfo.name.equals(pair.second) || basicInfo.name.isEmpty())) {
basicInfo.name = pair.first; // simple rename, fix order anyway
result.remove(basicInfo);
result.add(basicInfo);
}
else {
result.add(new MethodInfo(3, pair.first, pair.second, null, null)); // user method
}
}
else if (methodInfo == null) {
result.add(new MethodInfo(4, pair.first, null, null, null)); // method mixin
}
}
myMethods.put(rule, Pair.create(basicMethods, (Collection<MethodInfo>)result));
}
@Nullable
private String getRuleOrTokenNameForPsi(@NotNull PsiElement tree, @NotNull RuleGraphHelper.Cardinality type) {
String result;
if (!(tree instanceof BnfRule)) {
if (type.many()) return null; // do not generate token lists
IElementType effectiveType = getEffectiveType(tree);
if (effectiveType == BNF_STRING) {
result = mySimpleTokens.get(StringUtil.stripQuotesAroundValue(tree.getText()));
}
else if (effectiveType == BNF_REFERENCE_OR_TOKEN) {
result = tree.getText();
}
else {
result = null;
}
}
else {
BnfRule asRule = (BnfRule)tree;
result = asRule.getName();
if (StringUtil.isEmpty(getElementType(asRule))) return null;
}
return result;
}
public static class MethodInfo implements Comparable<MethodInfo> {
int type;
String name;
String path;
BnfRule rule;
RuleGraphHelper.Cardinality cardinality;
private MethodInfo(int type, String name, String path, BnfRule rule, RuleGraphHelper.Cardinality cardinality) {
this.type = type;
this.name = name;
this.path = path;
this.rule = rule;
this.cardinality = cardinality;
}
@Override
public int compareTo(@NotNull MethodInfo o) {
if (type != o.type) return type - o.type;
return name.compareTo(o.name);
}
@Override
public String toString() {
return "MethodInfo{" +
"type=" + type +
", name='" + name + '\'' +
", path='" + path + '\'' +
", rule=" + rule +
", cardinality=" + cardinality +
'}';
}
}
}