/*
* 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.generator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.NavigatablePsiElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.NameUtil;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.containers.JBTreeTraverser;
import com.intellij.util.containers.TreeTraversal;
import gnu.trove.TObjectHashingStrategy;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.actions.GenerateAction;
import org.intellij.grammar.java.JavaHelper;
import org.intellij.grammar.psi.*;
import org.intellij.grammar.psi.impl.GrammarUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import static java.lang.String.format;
import static org.intellij.grammar.generator.RuleGraphHelper.getSynonymTargetOrSelf;
import static org.intellij.grammar.generator.RuleGraphHelper.getTokenNameToTextMap;
import static org.intellij.grammar.psi.BnfTypes.BNF_SEQUENCE;
/**
* @author gregory
* Date: 16.07.11 10:41
*/
public class ParserGeneratorUtil {
private static final Object NULL = new Object();
private static final BnfExpression NULL_ATTR = new FakeBnfExpression("NULL");
private static final String RESERVED_SUFFIX = "_$";
private static final Set<String> JAVA_RESERVED = ContainerUtil.newTroveSet(
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class",
"const", "default", "do", "double", "else", "enum", "extends", "false", "final", "finally",
"float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long",
"native", "new", "null", "package", "private", "protected", "public", "return", "short", "static",
"strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true",
"try", "void", "volatile", "while", "continue");
@NotNull
public static String getRawClassName(@NotNull String name) {
return name.indexOf("<") < name.indexOf(">") ? name.substring(0, name.indexOf("<")) : name;
}
enum ConsumeType {
DEFAULT, FAST, SMART;
public String getMethodSuffix() {
return this == DEFAULT ? "" : StringUtil.capitalize(name().toLowerCase());
}
@NotNull
public static ConsumeType forRule(@NotNull BnfRule rule) {
String value = getAttribute(rule, KnownAttribute.CONSUME_TOKEN_METHOD);
for (ConsumeType method : values()) {
if (StringUtil.equalsIgnoreCase(value, method.name())) return method;
}
return ObjectUtils.chooseNotNull(forMethod(value), DEFAULT);
}
@Nullable
public static ConsumeType forMethod(String value) {
if ("consumeTokenFast".equals(value)) return FAST;
if ("consumeTokenSmart".equals(value)) return SMART;
if ("consumeToken".equals(value)) return DEFAULT;
return null;
}
}
@NotNull
public static <T extends Enum<T>> T enumFromString(@Nullable String value, @NotNull T def) {
try {
return value == null ? def : Enum.valueOf(def.getDeclaringClass(), Case.UPPER.apply(value).replace('-', '_'));
}
catch (Exception e) {
return def;
}
}
public static <T> T getGenerateOption(@NotNull PsiElement node, @NotNull KnownAttribute<T> attribute, @Nullable String currentValue) {
if (attribute.getDefaultValue() instanceof Boolean) {
if ("yes".equals(currentValue)) return (T)Boolean.TRUE;
if ("no".equals(currentValue)) return (T)Boolean.FALSE;
}
else if (attribute.getDefaultValue() instanceof Number) {
int value = StringUtil.parseInt(currentValue, -1);
if (value != -1) return (T)Integer.valueOf(value);
}
return getRootAttribute(node, attribute, null);
}
public static <T> T getRootAttribute(@NotNull PsiElement node, @NotNull KnownAttribute<T> attribute) {
return getRootAttribute(node, attribute, null);
}
public static <T> T getRootAttribute(@NotNull PsiElement node, @NotNull KnownAttribute<T> attribute, @Nullable String match) {
return ((BnfFile)node.getContainingFile()).findAttributeValue(null, attribute, match);
}
public static <T> T getAttribute(@NotNull BnfRule rule, @NotNull KnownAttribute<T> attribute) {
return getAttribute(rule, attribute, null);
}
@Nullable
public static <T> BnfAttr findAttribute(@NotNull BnfRule rule, @NotNull KnownAttribute<T> attribute) {
return ((BnfFile)rule.getContainingFile()).findAttribute(rule, attribute, null);
}
public static <T> T getAttribute(@NotNull BnfRule rule, @NotNull KnownAttribute<T> attribute, @Nullable String match) {
return ((BnfFile)rule.getContainingFile()).findAttributeValue(rule, attribute, match);
}
public static Object getAttributeValue(BnfExpression value) {
if (value == null) return null;
if (value == NULL_ATTR) return NULL;
if (value instanceof BnfReferenceOrToken) {
return getTokenValue((BnfReferenceOrToken)value);
}
else if (value instanceof BnfLiteralExpression) {
return getLiteralValue((BnfLiteralExpression)value);
}
else if (value instanceof BnfValueList) {
KnownAttribute.ListValue pairs = new KnownAttribute.ListValue();
for (BnfListEntry o : ((BnfValueList)value).getListEntryList()) {
PsiElement id = o.getId();
pairs.add(Pair.create(id == null? null : id.getText(), getLiteralValue(o.getLiteralExpression())));
}
return pairs;
}
return null;
}
@Nullable
public static String getLiteralValue(BnfStringLiteralExpression child) {
return getLiteralValue((BnfLiteralExpression)child);
}
@Nullable
public static <T> T getLiteralValue(BnfLiteralExpression child) {
if (child == null) return null;
PsiElement literal = PsiTreeUtil.getDeepestFirst(child);
String text = child.getText();
IElementType elementType = literal.getNode().getElementType();
if (elementType == BnfTypes.BNF_NUMBER) return (T)Integer.valueOf(text);
if (elementType == BnfTypes.BNF_STRING) {
String unquoted = GrammarUtil.unquote(text);
// in double-quoted strings: un-escape quotes only leaving the rest \ manageable
String result = text.charAt(0) == '"' ? unquoted.replaceAll("\\\\(\"|')", "$1") : unquoted;
return (T) result;
}
return null;
}
private static Object getTokenValue(BnfReferenceOrToken child) {
String text = child.getText();
if (text.equals("true") || text.equals("false")) return Boolean.parseBoolean(text);
if (text.equals("null")) return NULL;
return text;
}
public static boolean isTrivialNode(PsiElement element) {
return getTrivialNodeChild(element) != null;
}
public static BnfExpression getNonTrivialNode(BnfExpression initialNode) {
BnfExpression nonTrivialNode = initialNode;
for (BnfExpression e = initialNode, n = getTrivialNodeChild(e); n != null; e = n, n = getTrivialNodeChild(e)) {
nonTrivialNode = n;
}
return nonTrivialNode;
}
public static BnfExpression getTrivialNodeChild(PsiElement element) {
PsiElement child = null;
if (element instanceof BnfParenthesized) {
BnfExpression e = ((BnfParenthesized)element).getExpression();
if (element instanceof BnfParenExpression) {
child = e;
}
else {
BnfExpression c = e;
while (c instanceof BnfParenthesized) {
c = ((BnfParenthesized)c).getExpression();
}
if (c.getFirstChild() == null) {
child = e;
}
}
}
else if (element.getFirstChild() == element.getLastChild() &&
(element instanceof BnfChoice || element instanceof BnfSequence || element instanceof BnfExpression)) {
child = element.getFirstChild();
}
return child instanceof BnfExpression && !(child instanceof BnfLiteralExpression || child instanceof BnfReferenceOrToken) ?
(BnfExpression) child : null;
}
public static BnfExpression getEffectiveExpression(BnfFile file, BnfExpression tree) {
if (tree instanceof BnfReferenceOrToken) {
BnfRule rule = file.getRule(tree.getText());
if (rule != null) return rule.getExpression();
}
return tree;
}
public static IElementType getEffectiveType(PsiElement tree) {
if (tree instanceof BnfParenOptExpression) {
return BnfTypes.BNF_OP_OPT;
}
else if (tree instanceof BnfQuantified) {
final BnfQuantifier quantifier = ((BnfQuantified)tree).getQuantifier();
return PsiTreeUtil.getDeepestFirst(quantifier).getNode().getElementType();
}
else if (tree instanceof BnfPredicate) {
return ((BnfPredicate)tree).getPredicateSign().getFirstChild().getNode().getElementType();
}
else if (tree instanceof BnfStringLiteralExpression) {
return BnfTypes.BNF_STRING;
}
else if (tree instanceof BnfLiteralExpression) {
return tree.getFirstChild().getNode().getElementType();
}
else if (tree instanceof BnfParenExpression) {
return BnfTypes.BNF_SEQUENCE;
}
else {
return tree.getNode().getElementType();
}
}
public static List<BnfExpression> getChildExpressions(@Nullable BnfExpression node) {
return PsiTreeUtil.getChildrenOfTypeAsList(node, BnfExpression.class);
}
public static String getFuncName(@NotNull BnfRule r) {
String name = toIdentifier(r.getName(), null, Case.AS_IS);
return JAVA_RESERVED.contains(name) ? name + RESERVED_SUFFIX : name;
}
public static String getNextName(@NotNull String funcName, int i) {
return StringUtil.trimEnd(funcName, RESERVED_SUFFIX) + "_" + i;
}
@NotNull
public static String getGetterName(@NotNull String text) {
return toIdentifier(text, NameFormat.from("get"), Case.CAMEL);
}
@TestOnly
@NotNull
public static String toIdentifier(@NotNull String text, @Nullable NameFormat format, @NotNull Case cas) {
if (text.isEmpty()) return "";
String fixed = text.replaceAll("[^:\\p{javaJavaIdentifierPart}]", "_");
boolean allCaps = Case.UPPER.apply(fixed).equals(fixed);
StringBuilder sb = new StringBuilder();
if (!Character.isJavaIdentifierStart(fixed.charAt(0)) && sb.length() == 0) sb.append("_");
String[] strings = NameUtil.nameToWords(fixed);
for (int i = 0, len = strings.length; i < len; i++) {
String s = strings[i];
if (cas == Case.CAMEL && s.startsWith("_") && !(i == 0 || i == len - 1)) continue;
if (cas == Case.UPPER && !s.startsWith("_") && !(i == 0 || StringUtil.endsWith(sb, "_"))) sb.append("_");
if (cas == Case.CAMEL && !allCaps && Case.UPPER.apply(s).equals(s)) sb.append(s);
else sb.append(cas.apply(s));
}
return format == null ? sb.toString() : format.apply(sb.toString());
}
public static String getPsiPackage(BnfFile file) {
return getRootAttribute(file, KnownAttribute.PSI_PACKAGE);
}
public static String getPsiImplPackage(BnfFile file) {
return getRootAttribute(file, KnownAttribute.PSI_IMPL_PACKAGE);
}
@NotNull
public static NameFormat getPsiClassFormat(BnfFile file) {
return NameFormat.from(getRootAttribute(file, KnownAttribute.PSI_CLASS_PREFIX));
}
@NotNull
public static NameFormat getPsiImplClassFormat(BnfFile file) {
String prefix = getRootAttribute(file, KnownAttribute.PSI_CLASS_PREFIX);
String suffix = getRootAttribute(file, KnownAttribute.PSI_IMPL_CLASS_SUFFIX);
return NameFormat.from(prefix + "/" + StringUtil.notNullize(suffix));
}
@NotNull
public static String getRulePsiClassName(@NotNull BnfRule rule, @Nullable NameFormat format) {
return toIdentifier(rule.getName(), format, Case.CAMEL);
}
public static String getQualifiedRuleClassName(BnfRule rule, boolean impl) {
BnfFile file = (BnfFile)rule.getContainingFile();
String packageName = impl ? getPsiImplPackage(file) : getPsiPackage(file);
NameFormat format = impl ? getPsiImplClassFormat(file) : getPsiClassFormat(file);
return packageName + "." + getRulePsiClassName(rule, format);
}
@NotNull
public static List<NavigatablePsiElement> findRuleImplMethods(@NotNull JavaHelper helper,
@Nullable String psiImplUtilClass,
@Nullable String methodName,
@Nullable BnfRule rule) {
List<NavigatablePsiElement> methods = Collections.emptyList();
if (rule == null) return methods;
String selectedSuperClass = null;
main: for (String ruleClass : getRuleClasses(rule)) {
for (String utilClass = psiImplUtilClass; utilClass != null; utilClass = helper.getSuperClassName(utilClass)) {
methods = helper.findClassMethods(utilClass, JavaHelper.MethodType.STATIC, methodName, -1, ruleClass);
selectedSuperClass = ruleClass;
if (!methods.isEmpty()) break main;
}
}
return filterOutShadowedRuleImplMethods(selectedSuperClass, methods, helper);
}
@NotNull
private static List<NavigatablePsiElement> filterOutShadowedRuleImplMethods(String selectedClass,
List<NavigatablePsiElement> methods,
@NotNull JavaHelper helper) {
if (methods.size() <= 1) return methods;
// filter out less specific methods
// todo move to JavaHelper
List<NavigatablePsiElement> result = ContainerUtil.newArrayList(methods);
Map<String, NavigatablePsiElement> prototypes = ContainerUtil.newLinkedHashMap();
for (NavigatablePsiElement m2 : methods) {
List<String> types = helper.getMethodTypes(m2);
String proto = m2.getName() + types.subList(3, types.size());
NavigatablePsiElement m1 = prototypes.get(proto);
if (m1 == null) {
prototypes.put(proto, m2);
continue;
}
String type1 = helper.getMethodTypes(m1).get(1);
String type2 = types.get(1);
if (Comparing.equal(type1, type2)) continue;
for (String s = selectedClass; s != null; s = helper.getSuperClassName(s)) {
if (Comparing.equal(type1, s)) {
result.remove(m2);
}
else if (Comparing.equal(type2, s)) {
result.remove(m1);
}
else continue;
break;
}
}
return result;
}
@NotNull
public static Set<String> getRuleClasses(@NotNull BnfRule rule) {
BnfFile file = (BnfFile)rule.getContainingFile();
Set<String> result = ContainerUtil.newLinkedHashSet();
String superClassName = getSuperClassName(file, rule, getPsiImplPackage(file), getPsiImplClassFormat(file));
String implSuper = StringUtil.notNullize(getAttribute(rule, KnownAttribute.MIXIN), superClassName);
String aPackage = getPsiPackage(file);
result.add(getQualifiedRuleClassName(rule, false));
result.add(getQualifiedRuleClassName(rule, true));
result.add(superClassName);
result.add(implSuper);
result.addAll(getSuperInterfaceNames(file, rule, aPackage, getPsiClassFormat(file)));
return result;
}
@Nullable
static BnfRule getTopSuperRule(@NotNull BnfFile file, @Nullable BnfRule rule) {
Set<BnfRule> visited = ContainerUtil.newHashSet();
BnfRule cur = rule, next = rule;
for (; next != null && cur != null; cur = !visited.add(next) ? null : next) {
next = getSynonymTargetOrSelf(cur);
if (next != cur) continue;
if (cur != rule) break; // do not search for elementType any further
String attr = getAttribute(cur, KnownAttribute.EXTENDS);
//noinspection StringEquality
next = attr != KnownAttribute.EXTENDS.getDefaultValue() ? file.getRule(attr) : null;
if (next == null && attr != null) break;
}
return cur;
}
@NotNull
static List<String> getSuperInterfaceNames(BnfFile file, BnfRule rule, String psiPackage, NameFormat classPrefix) {
List<String> strings = ContainerUtil.newArrayList();
List<String> topRuleImplements = Collections.emptyList();
String topRuleClass = null;
BnfRule topSuper = getTopSuperRule(file, rule);
boolean withPackage = psiPackage.isEmpty();
if (topSuper != null && topSuper != rule) {
topRuleImplements = getAttribute(topSuper, KnownAttribute.IMPLEMENTS).asStrings();
topRuleClass =
StringUtil.nullize((withPackage ? "" : psiPackage + ".") + getRulePsiClassName(topSuper, classPrefix));
if (!StringUtil.isEmpty(topRuleClass)) strings.add(topRuleClass);
}
List<String> rootImplements = getRootAttribute(file, KnownAttribute.IMPLEMENTS).asStrings();
List<String> ruleImplements = getAttribute(rule, KnownAttribute.IMPLEMENTS).asStrings();
for (String className : ruleImplements) {
if (className == null) continue;
BnfRule superIntfRule = file.getRule(className);
if (superIntfRule != null) {
strings.add((withPackage ? "" : psiPackage + ".") + getRulePsiClassName(superIntfRule, classPrefix));
}
else if (!topRuleImplements.contains(className) &&
(topRuleClass == null || !rootImplements.contains(className))) {
if (strings.size() == 1 && topSuper == null) {
strings.add(0, className);
}
else {
strings.add(className);
}
}
}
return strings;
}
@NotNull
static String getSuperClassName(@NotNull BnfFile file, @Nullable BnfRule rule, String psiPackage, NameFormat format) {
BnfRule topSuper = getTopSuperRule(file, rule);
return topSuper == null ? getRootAttribute(file, KnownAttribute.EXTENDS) :
topSuper == rule ? getAttribute(rule, KnownAttribute.EXTENDS) :
psiPackage + "." + getRulePsiClassName(topSuper, format);
}
@Nullable
public static String getRuleDisplayName(BnfRule rule, boolean force) {
String s = getRuleDisplayNameRaw(rule, force);
return StringUtil.isEmpty(s) ? null : "<" + s + ">";
}
@Nullable
private static String getRuleDisplayNameRaw(BnfRule rule, boolean force) {
String name = getAttribute(rule, KnownAttribute.NAME);
BnfRule realRule = rule;
if (name != null) {
realRule = ((BnfFile)rule.getContainingFile()).getRule(name);
if (realRule != null) name = getAttribute(realRule, KnownAttribute.NAME);
}
if (name != null || (!force && realRule == rule)) {
return name;
}
else {
return Case.LOWER.apply(StringUtil.join(NameUtil.splitNameIntoWords(realRule.getName()), " "));
}
}
public static String getElementType(BnfRule rule, @NotNull Case cas) {
String elementType = getAttribute(rule, KnownAttribute.ELEMENT_TYPE);
if ("".equals(elementType)) return "";
NameFormat prefix = NameFormat.from(getAttribute(rule, KnownAttribute.ELEMENT_TYPE_PREFIX));
return toIdentifier(elementType != null ? elementType : rule.getName(), prefix, cas);
}
public static String getTokenType(BnfFile file, String token, @NotNull Case cas) {
NameFormat format = NameFormat.from(getRootAttribute(file, KnownAttribute.ELEMENT_TYPE_PREFIX));
String fixed = cas.apply(token.replaceAll("[^:\\p{javaJavaIdentifierPart}]", "_"));
return format == null ? fixed : format.apply(fixed);
}
public static Collection<BnfRule> getSortedPublicRules(Set<PsiElement> accessors) {
Map<String, BnfRule> result = ContainerUtil.newTreeMap();
for (PsiElement tree : accessors) {
if (tree instanceof BnfRule) {
BnfRule rule = (BnfRule)tree;
if (!Rule.isPrivate(rule)) result.put(rule.getName(), rule);
}
}
return result.values();
}
public static Collection<BnfExpression> getSortedTokens(Set<PsiElement> accessors) {
Map<String, BnfExpression> result = ContainerUtil.newTreeMap();
for (PsiElement tree : accessors) {
if (!(tree instanceof BnfReferenceOrToken || tree instanceof BnfLiteralExpression)) continue;
result.put(tree.getText(), (BnfExpression)tree);
}
return result.values();
}
public static Collection<LeafPsiElement> getSortedExternalRules(Set<PsiElement> accessors) {
Map<String, LeafPsiElement> result = ContainerUtil.newTreeMap();
for (PsiElement tree : accessors) {
if (!(tree instanceof LeafPsiElement)) continue;
result.put(tree.getText(), (LeafPsiElement) tree);
}
return result.values();
}
public static List<BnfRule> topoSort(@NotNull Collection<BnfRule> rules, @NotNull RuleGraphHelper ruleGraph) {
Set<BnfRule> rulesSet = ContainerUtil.newHashSet(rules);
return new JBTreeTraverser<BnfRule>(
rule -> JBIterable.from(ruleGraph.getSubRules(rule)).filter(rulesSet::contains))
.withRoots(ContainerUtil.reverse(ContainerUtil.newArrayList(rules)))
.withTraversal(TreeTraversal.POST_ORDER_DFS)
.unique()
.toList();
}
public static void addWarning(Project project, String s, Object... args) {
addWarning(project, format(s, args));
}
public static void addWarning(Project project, String text) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
//noinspection UseOfSystemOutOrSystemErr
System.out.println(text);
}
else {
GenerateAction.LOG_GROUP.createNotification(text, MessageType.WARNING).notify(project);
}
}
public static void checkClassAvailability(@NotNull BnfFile file, @Nullable String className, @Nullable String description) {
if (StringUtil.isEmpty(className)) return;
JavaHelper javaHelper = JavaHelper.getJavaHelper(file);
if (javaHelper.findClass(className) == null) {
String tail = StringUtil.isEmpty(description) ? "" : " (" + description + ")";
addWarning(file.getProject(), className + " class not found" + tail);
}
}
public static boolean isRegexpToken(@NotNull String tokenText) {
return tokenText.startsWith("regexp:");
}
public static String getRegexpTokenRegexp(@NotNull String tokenText) {
return tokenText.substring("regexp:".length());
}
public static boolean isTokenSequence(@NotNull BnfRule rule, @Nullable BnfExpression node) {
if (node == null || ConsumeType.forRule(rule) != ConsumeType.DEFAULT) return false;
if (getEffectiveType(node) != BNF_SEQUENCE) return false;
BnfFile file = (BnfFile) rule.getContainingFile();
for (PsiElement child : getChildExpressions(node)) {
String text = child.getText();
String tokenName = child instanceof BnfStringLiteralExpression ? RuleGraphHelper.getTokenTextToNameMap(file).get(GrammarUtil.unquote(child.getText())) :
child instanceof BnfReferenceOrToken && file.getRule(text) == null ? text : null;
if (tokenName == null) return false;
}
return true;
}
public static void appendTokenTypes(StringBuilder sb, List<String> tokenTypes) {
for (int count = 0, line = 0, size = tokenTypes.size(); count < size; count++) {
boolean newLine = line == 0 && count == 2 || line > 0 && (count - 2) % 6 == 0;
newLine &= (size - count) > 2;
if (count > 0) sb.append(",").append(newLine ? "\n" : " ");
sb.append(tokenTypes.get(count));
if (newLine) line ++;
}
}
public static Map<String, String> collectTokenPattern2Name(@NotNull final BnfFile file,
final boolean createTokenIfMissing,
@NotNull final Map<String, String> map,
@Nullable Set<String> usedInGrammar) {
final Set<String> usedNames = usedInGrammar != null ? usedInGrammar : ContainerUtil.newLinkedHashSet();
final Map<String, String> origTokens = RuleGraphHelper.getTokenTextToNameMap(file);
final Pattern pattern = getAllTokenPattern(origTokens);
final int[] autoCount = {0};
final Set<String> origTokenNames = getTokenNameToTextMap(file).keySet();
BnfVisitor<Void> visitor = new BnfVisitor<Void>() {
@Override
public Void visitStringLiteralExpression(@NotNull BnfStringLiteralExpression o) {
String text = o.getText();
String tokenText = GrammarUtil.unquote(text);
// add auto-XXX token for all unmatched strings to avoid BAD_CHARACTER's
if (createTokenIfMissing &&
!usedNames.contains(tokenText) &&
!StringUtil.isJavaIdentifier(tokenText) &&
(pattern == null || !pattern.matcher(tokenText).matches())) {
String tokenName = "_AUTO_" + (autoCount[0]++);
usedNames.add(text);
map.put(tokenText, tokenName);
}
else {
ContainerUtil.addIfNotNull(usedNames, origTokens.get(tokenText));
}
return null;
}
@Override
public Void visitReferenceOrToken(@NotNull BnfReferenceOrToken o) {
if (GrammarUtil.isExternalReference(o)) return null;
BnfRule rule = o.resolveRule();
if (rule != null) return null;
String tokenName = o.getText();
if (usedNames.add(tokenName) && !origTokenNames.contains(tokenName)) {
map.put(tokenName, tokenName);
}
return null;
}
};
for (BnfExpression o : GrammarUtil.bnfTraverserNoAttrs(file).filter(BnfExpression.class)) {
o.accept(visitor);
}
// fix ordering: origTokens go _after_ to handle keywords correctly
for (String tokenText : origTokens.keySet()) {
String tokenName = origTokens.get(tokenText);
map.remove(tokenText);
map.put(tokenText, tokenName != null || !createTokenIfMissing ? tokenName : "_AUTO_" + (autoCount[0]++));
}
return map;
}
public static class Rule {
public static boolean isPrivate(BnfRule node) {
return hasModifier(node, "private");
}
public static boolean isExternal(BnfRule node) {
return hasModifier(node, "external");
}
public static boolean isMeta(BnfRule node) {
return hasModifier(node, "meta");
}
public static boolean isLeft(BnfRule node) {
return hasModifier(node, "left");
}
public static boolean isInner(BnfRule node) {
return hasModifier(node, "inner");
}
public static boolean isFake(BnfRule node) {
return hasModifier(node, "fake");
}
public static boolean isUpper(BnfRule node) {
return hasModifier(node, "upper");
}
private static boolean hasModifier(@Nullable BnfRule rule, @NotNull String s) {
if (rule == null) return false;
for (BnfModifier modifier : rule.getModifierList()) {
if (s.equals(modifier.getText())) return true;
}
return false;
}
public static PsiElement firstNotTrivial(BnfRule rule) {
for (PsiElement tree = rule.getExpression(); tree != null; tree = PsiTreeUtil.getChildOfType(tree, BnfExpression.class)) {
if (!isTrivialNode(tree)) return tree;
}
return null;
}
public static BnfRule of(BnfExpression expr) {
return PsiTreeUtil.getParentOfType(expr, BnfRule.class);
}
}
@Nullable
public static String quote(@Nullable String text) {
if (text == null) return null;
return "\"" + text + "\"";
}
@Nullable
public static Pattern compilePattern(String text) {
try {
return Pattern.compile(text);
}
catch (PatternSyntaxException e) {
return null;
}
}
public static boolean matchesAny(String regexp, String... text) {
try {
Pattern p = Pattern.compile(regexp);
for (String s : text) {
if (p.matcher(s).matches()) return true;
}
}
catch (PatternSyntaxException ignored) {
}
return false;
}
@Nullable
public static Pattern getAllTokenPattern(Map<String, String> tokens) {
String allRegexp = "";
for (String pattern : tokens.keySet()) {
if (!isRegexpToken(pattern)) continue;
if (allRegexp.length() > 0) allRegexp += "|";
allRegexp += getRegexpTokenRegexp(pattern);
}
return compilePattern(allRegexp);
}
public static class PinMatcher {
public final BnfRule rule;
public final String funcName;
public final Object pinValue;
private final int pinIndex;
private final Pattern pinPattern;
public PinMatcher(BnfRule rule, IElementType type, String funcName) {
this.rule = rule;
this.funcName = funcName;
pinValue = type == BNF_SEQUENCE ? getAttribute(rule, KnownAttribute.PIN, funcName) : null;
pinIndex = pinValue instanceof Integer? (Integer)pinValue : -1;
pinPattern = pinValue instanceof String ? compilePattern((String) pinValue) : null;
}
public boolean active() { return pinIndex > -1 || pinPattern != null; }
public boolean matches(int i, BnfExpression child) {
return i == pinIndex - 1 || pinPattern != null && pinPattern.matcher(child.getText()).matches();
}
public boolean shouldGenerate(List<BnfExpression> children) {
// do not check last expression, last item pin is trivial
for (int i = 0, size = children.size(); i < size - 1; i++) {
if (matches(i, children.get(i))) return true;
}
return false;
}
}
public static String getParametersString(List<String> paramsTypes,
int offset,
int mask,
Function<String, String> substitutor,
Function<String, String> shortener) {
StringBuilder sb = new StringBuilder();
for (int i = offset; i < paramsTypes.size(); i += 2) {
if (i > offset) sb.append(", ");
String type = paramsTypes.get(i);
String name = paramsTypes.get(i + 1);
if (type.startsWith("<") && type.endsWith(">")) {
type = substitutor.fun(type);
}
if (BnfConstants.AST_NODE_CLASS.equals(type)) name = "node";
if (type.endsWith("ElementType")) name = "type";
if (type.endsWith("Stub")) name = "stub";
if ((mask & 1) == 1) sb.append(shortener.fun(type));
if ((mask & 3) == 3) sb.append(" ");
if ((mask & 2) == 2) sb.append(name);
}
return sb.toString();
}
public static class NameFormat {
final static NameFormat EMPTY = new NameFormat("");
final String prefix;
final String suffix;
public static NameFormat from(@Nullable String format) {
return StringUtil.isEmpty(format) ? EMPTY : new NameFormat(format);
}
private NameFormat(@Nullable String format) {
JBIterable<String> parts = JBIterable.of(format == null ? null : format.split("/"));
prefix = parts.get(0);
suffix = StringUtil.join(parts.skip(1), "");
}
public String apply(String s) {
if (prefix != null) s = prefix + s;
if (suffix != null) s += suffix;
return s;
}
public String strip(String s) {
if (prefix != null && s.startsWith(prefix)) s = s.substring(prefix.length());
if (suffix != null && s.endsWith(suffix)) s = s.substring(0, s.length() - suffix.length());
return s;
}
}
private static final TObjectHashingStrategy<PsiElement> TEXT_STRATEGY = new TObjectHashingStrategy<PsiElement>() {
@Override
public int computeHashCode(PsiElement e) {
return e.getText().hashCode();
}
@Override
public boolean equals(PsiElement e1, PsiElement e2) {
return Comparing.equal(e1.getText(), e2.getText());
}
};
public static <T extends PsiElement> TObjectHashingStrategy<T> textStrategy() {
return (TObjectHashingStrategy<T>)TEXT_STRATEGY;
}
}