/*
* 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.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.NavigatablePsiElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.Functions;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.analysis.BnfFirstNextAnalyzer;
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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import static java.lang.String.format;
import static org.intellij.grammar.generator.BnfConstants.*;
import static org.intellij.grammar.generator.ParserGeneratorUtil.*;
import static org.intellij.grammar.generator.RuleGraphHelper.Cardinality.*;
import static org.intellij.grammar.generator.RuleGraphHelper.getSynonymTargetOrSelf;
import static org.intellij.grammar.psi.BnfTypes.*;
/**
* @author gregory
* Date 16.07.11 10:41
*/
public class ParserGenerator {
public static final Logger LOG = Logger.getInstance("ParserGenerator");
private static final String TYPE_TEXT_SEPARATORS = "<>,[]";
private final Map<String, String> myRuleParserClasses = ContainerUtil.newTreeMap();
private final Map<String, String> myParserLambdas = ContainerUtil.newTreeMap();
private final Set<String> myPackageClasses = ContainerUtil.newTreeSet();
private final Map<String, String> mySimpleTokens;
private final Set<String> myTokensUsedInGrammar = ContainerUtil.newLinkedHashSet();
private final Set<String> myFakeRulesWithType = ContainerUtil.newHashSet();
private final Set<String> myAbstractRules = ContainerUtil.newHashSet();
private final Map<String, String> myRulesStubNames = ContainerUtil.newHashMap();
private final BnfFile myFile;
private final String mySourcePath;
private final String myOutputPath;
private final String myGrammarRoot;
private final String myGrammarRootParser;
private final String myParserUtilClass;
private final String myPsiImplUtilClass;
private final String myPsiTreeUtilClass;
private final NameFormat myPsiClassFormat;
private final NameFormat myPsiImplClassFormat;
private final String myVisitorClassName;
private int myOffset;
private PrintWriter myOut;
private Function<String, String> myShortener;
private final RuleGraphHelper myGraphHelper;
private final ExpressionHelper myExpressionHelper;
private final RuleMethodsHelper myRulesMethodsHelper;
private final JavaHelper myJavaHelper;
final Names N;
final GenOptions G;
public ParserGenerator(BnfFile tree, String sourcePath, String outputPath) {
myFile = tree;
mySourcePath = sourcePath;
myOutputPath = outputPath;
G = new GenOptions(myFile);
N = G.names;
List<BnfRule> rules = tree.getRules();
myGrammarRoot = rules.isEmpty() ? null : rules.get(0).getName();
for (BnfRule r : rules) {
myRuleParserClasses.put(r.getName(), getAttribute(r, KnownAttribute.PARSER_CLASS));
}
myGrammarRootParser = myGrammarRoot == null? null : myRuleParserClasses.get(myGrammarRoot);
myPsiClassFormat = getPsiClassFormat(myFile);
myPsiImplClassFormat = getPsiImplClassFormat(myFile);
myParserUtilClass = getRootAttribute(myFile, KnownAttribute.PARSER_UTIL_CLASS);
myPsiImplUtilClass = getRootAttribute(myFile, KnownAttribute.PSI_IMPL_UTIL_CLASS);
myPsiTreeUtilClass = getRootAttribute(myFile, KnownAttribute.PSI_TREE_UTIL_CLASS);
String tmpVisitorClass = getRootAttribute(myFile, KnownAttribute.PSI_VISITOR_NAME);
myVisitorClassName = !G.generateVisitor || StringUtil.isEmpty(tmpVisitorClass) ? null :
!tmpVisitorClass.equals(myPsiClassFormat.strip(tmpVisitorClass)) ? tmpVisitorClass :
myPsiClassFormat.apply("") + tmpVisitorClass;
mySimpleTokens = ContainerUtil.newLinkedHashMap(RuleGraphHelper.getTokenTextToNameMap(myFile));
myGraphHelper = RuleGraphHelper.getCached(myFile);
myExpressionHelper = new ExpressionHelper(myFile, myGraphHelper, true);
myRulesMethodsHelper = new RuleMethodsHelper(myGraphHelper, myExpressionHelper, mySimpleTokens, G);
myJavaHelper = JavaHelper.getJavaHelper(myFile);
calcFakeRulesWithType();
calcRulesStubNames();
calcAbstractRules();
}
private void calcAbstractRules() {
Set<String> reusedRules = ContainerUtil.newHashSet();
for (BnfRule rule : myFile.getRules()) {
String elementType = getAttribute(rule, KnownAttribute.ELEMENT_TYPE);
BnfRule r = elementType != null ? myFile.getRule(elementType) : null;
if (r != null && r != rule) reusedRules.add(r.getName());
}
for (BnfRule rule : myFile.getRules()) {
if (reusedRules.contains(rule.getName())) continue;
if (myGrammarRoot.equals(rule.getName())) continue;
if (!rule.getModifierList().isEmpty()) continue;
if (getAttribute(rule, KnownAttribute.RECOVER_WHILE) != null) continue;
if (!getAttribute(rule, KnownAttribute.HOOKS).isEmpty()) continue;
if (myGraphHelper.canCollapse(rule) && myGraphHelper.getFor(rule).isEmpty()) {
myAbstractRules.add(rule.getName());
}
}
}
private void calcFakeRulesWithType() {
for (BnfRule rule : myFile.getRules()) {
BnfRule r = myFile.getRule(getAttribute(rule, KnownAttribute.ELEMENT_TYPE));
if (Rule.isFake(r)) {
myFakeRulesWithType.add(r.getName());
}
}
}
private void calcRulesStubNames() {
for (BnfRule rule : myFile.getRules()) {
String stubClass = getAttribute(rule, KnownAttribute.STUB_CLASS);
if (stubClass == null) {
BnfRule topSuperRule = getTopSuperRule(myFile, rule);
stubClass = topSuperRule == null ? null : getAttribute(topSuperRule, KnownAttribute.STUB_CLASS);
}
String implSuper = StringUtil.notNullize(getAttribute(rule, KnownAttribute.MIXIN),
getSuperClassName(myFile, rule, "", myPsiClassFormat));
String implSuperRaw = getRawClassName(implSuper);
String stubName =
StringUtil.isNotEmpty(stubClass) ? stubClass :
implSuper.indexOf("<") < implSuper.indexOf(">") && !myJavaHelper.findClassMethods(implSuperRaw, JavaHelper.MethodType.INSTANCE, "getParentByStub", 0).isEmpty() ?
implSuper.substring(implSuper.indexOf("<") + 1, implSuper.indexOf(">")) : null;
if (StringUtil.isNotEmpty(stubName)) {
myRulesStubNames.put(rule.getName(), stubName);
}
}
}
@NotNull
private Map<String, String> calcRealSuperClasses(Map<String, BnfRule> sortedPsiRules, String psiImplPackage) {
Map<String, String> realSuperClasses = ContainerUtil.newHashMap();
for (String ruleName : sortedPsiRules.keySet()) {
BnfRule rule = ObjectUtils.assertNotNull(myFile.getRule(ruleName));
String superRuleClass = getSuperClassName(myFile, rule, psiImplPackage, myPsiImplClassFormat);
String stubName = myRulesStubNames.get(rule.getName());
String adjustedSuperRuleClass =
StringUtil.isEmpty(stubName) ? superRuleClass :
AST_WRAPPER_PSI_ELEMENT_CLASS.equals(superRuleClass) ? STUB_BASED_PSI_ELEMENT_BASE + "<" + stubName + ">" :
superRuleClass.contains("?") ? superRuleClass.replaceAll("\\?", stubName) : superRuleClass;
// mixin attribute overrides "extends":
String realSuper = StringUtil.notNullize(getAttribute(rule, KnownAttribute.MIXIN), adjustedSuperRuleClass);
realSuperClasses.put(ruleName, realSuper);
}
return realSuperClasses;
}
private void openOutput(String className) throws IOException {
File file = new File(myOutputPath, className.replace('.', File.separatorChar) + ".java");
myOut = openOutputInner(file);
}
protected PrintWriter openOutputInner(File file) throws IOException {
//noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
return new PrintWriter(new FileOutputStream(file));
}
private void closeOutput() {
myOut.close();
}
public void out(String s, Object... args) {
out(format(s, args));
}
public void out(String s) {
int length = s.length();
if (length == 0) {
myOut.println();
return;
}
boolean newStatement = true;
for (int start = 0, end; start < length; start = end + 1) {
boolean isComment = s.startsWith("//", start);
end = StringUtil.indexOf(s, '\n', start, length);
if (end == -1) end = length;
String substring = s.substring(start, end);
if (!isComment && substring.startsWith("}")) myOffset--;
if (myOffset > 0) {
myOut.print(StringUtil.repeat(" ", newStatement ? myOffset : myOffset + 1));
}
if (!isComment && substring.endsWith("{")) myOffset++;
myOut.println(substring);
newStatement = isComment || substring.endsWith(";") || substring.endsWith("{") || substring.endsWith("}");
}
}
public void newLine() {
out("");
}
public void generate() throws IOException {
{
generateParser();
}
Map<String, BnfRule> sortedCompositeTypes = ContainerUtil.newTreeMap();
Map<String, BnfRule> sortedPsiRules = ContainerUtil.newTreeMap();
for (BnfRule rule : myFile.getRules()) {
if (!RuleGraphHelper.hasPsiClass(rule)) continue;
String elementType = getElementType(rule);
if (StringUtil.isEmpty(elementType)) continue;
if (sortedCompositeTypes.containsKey(elementType)) continue;
if (!Rule.isFake(rule) || myFakeRulesWithType.contains(rule.getName())) {
sortedCompositeTypes.put(elementType, rule);
}
sortedPsiRules.put(rule.getName(), rule);
}
if (myGrammarRoot != null && (G.generateTokenTypes || G.generateElementTypes || G.generatePsi && G.generatePsiFactory)) {
String className = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS);
openOutput(className);
try {
generateElementTypesHolder(className, sortedCompositeTypes);
}
finally {
closeOutput();
}
}
if (G.generatePsi) {
checkClassAvailability(myFile, myPsiImplUtilClass, "PSI method signatures will not be detected");
myRulesMethodsHelper.buildMaps(sortedPsiRules.values());
for (BnfRule r : sortedPsiRules.values()) {
myPackageClasses.add(getRulePsiClassName(r, myPsiClassFormat));
}
Map<String, String> infClasses = ContainerUtil.newTroveMap();
String psiPackage = getPsiPackage(myFile);
String psiImplPackage = getPsiImplPackage(myFile);
for (String ruleName : sortedPsiRules.keySet()) {
BnfRule rule = ObjectUtils.assertNotNull(myFile.getRule(ruleName));
String psiClass = psiPackage + "." + getRulePsiClassName(rule, myPsiClassFormat);
infClasses.put(ruleName, psiClass);
openOutput(psiClass);
try {
generatePsiIntf(rule, psiClass, getSuperInterfaceNames(myFile, rule, psiPackage, myPsiClassFormat));
}
finally {
closeOutput();
}
}
Map<String, String> realSuperClasses = calcRealSuperClasses(sortedPsiRules, psiImplPackage);
for (String ruleName : sortedPsiRules.keySet()) {
BnfRule rule = ObjectUtils.assertNotNull(myFile.getRule(ruleName));
String psiImplClass = psiImplPackage + "." + getRulePsiClassName(rule, myPsiImplClassFormat);
openOutput(psiImplClass);
try {
generatePsiImpl(rule, psiImplClass, infClasses.get(ruleName), realSuperClasses);
}
finally {
closeOutput();
}
}
if (myVisitorClassName != null && myGrammarRoot != null) {
String psiClass = psiPackage + "." + myVisitorClassName;
openOutput(psiClass);
try {
generateVisitor(psiClass, sortedPsiRules);
}
finally {
closeOutput();
}
}
}
}
private void generateVisitor(String psiClass, Map<String, BnfRule> sortedRules) {
String superIntf = ObjectUtils.notNull(ContainerUtil.getFirstItem(getRootAttribute(myFile, KnownAttribute.IMPLEMENTS)),
KnownAttribute.IMPLEMENTS.getDefaultValue().get(0)).second;
String shortSuperIntf = StringUtil.getShortName(superIntf);
List<String> imports = ContainerUtil.newArrayList("org.jetbrains.annotations.*", PSI_ELEMENT_VISITOR_CLASS, superIntf);
MultiMap<String, String> supers = MultiMap.createSmart();
for (BnfRule rule : sortedRules.values()) {
supers.putValues(rule.getName(), getSuperInterfaceNames(myFile, rule, StringUtil.getPackageName(psiClass), myPsiClassFormat));
}
{
// ensure only public supers are exposed, replace non-public with default super-intf for simplicity
Map<String, String> replacements = ContainerUtil.newHashMap();
Set<String> visited = ContainerUtil.newHashSet();
for (String s : supers.values()) {
if (!visited.add(s)) continue;
NavigatablePsiElement aClass = myJavaHelper.findClass(s);
if (aClass != null && !myJavaHelper.isPublic(aClass)) {
replacements.put(s, superIntf);
}
}
for (String key : supers.keySet()) {
for (ListIterator<String> it = ((List<String>)supers.get(key)).listIterator(); it.hasNext(); ) {
String s = replacements.get(it.next());
if (s != null) {
if (s.isEmpty()) it.remove();
else it.set(s);
}
}
}
}
imports.addAll(supers.values());
String r = G.visitorValue != null ? "<" + G.visitorValue + ">" : "";
String t = G.visitorValue != null ? G.visitorValue : "void";
String ret = G.visitorValue != null ? "return " : "";
generateClassHeader(psiClass + r, imports, "", Java.CLASS, PSI_ELEMENT_VISITOR_CLASS);
Set<String> visited = new HashSet<>();
Set<String> all = new TreeSet<>();
for (BnfRule rule : sortedRules.values()) {
String methodName = getRulePsiClassName(rule, null);
visited.add(methodName);
out("public " + t + " visit" + methodName + "(@NotNull " + getRulePsiClassName(rule, myPsiClassFormat) + " o) {");
boolean first = true;
for (String top : supers.get(rule.getName())) {
if (!first && top.equals(superIntf)) continue;
int trimIdx = StringUtil.indexOfAny(top, TYPE_TEXT_SEPARATORS); // trim generics
top = myShortener.fun(trimIdx > 0 ? top.substring(0, trimIdx) : top);
if (first) all.add(top);
top = myPsiClassFormat.strip(top);
String text = "visit" + top + "(o);";
if (first) {
out(ret + text);
}
else {
out("// " + text);
}
if (first) first = false;
}
out("}");
newLine();
}
all.remove(shortSuperIntf);
for (String top : JBIterable.from(all).append(shortSuperIntf)) {
String methodName = myPsiClassFormat.strip(top);
if (visited.contains(methodName)) continue;
out("public " + t + " visit" + methodName + "(@NotNull " + myShortener.fun(top) + " o) {");
if (!methodName.equals(top) && !top.equals(shortSuperIntf)) {
out(ret + "visit" + myPsiClassFormat.strip(shortSuperIntf) + "(o);");
}
else {
String superPrefix = methodName.equals("Element") ? "super." : "";
out(superPrefix + "visitElement(o);");
if (G.visitorValue != null) out(ret + "null;");
}
out("}");
newLine();
}
out("}");
}
public void generateParser() throws IOException {
for (String className : new TreeSet<>(myRuleParserClasses.values())) {
Map<String, BnfRule> map = new TreeMap<>();
for (String ruleName : myRuleParserClasses.keySet()) {
if (className.equals(myRuleParserClasses.get(ruleName))) {
map.put(ruleName, myFile.getRule(ruleName));
}
}
openOutput(className);
try {
generateParser(className, map.keySet());
}
finally {
closeOutput();
}
}
}
public void generateParser(String parserClass, final Set<String> ownRuleNames) {
String elementTypeHolderClass = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS);
List<String> parserImports = getRootAttribute(myFile, KnownAttribute.PARSER_IMPORTS).asStrings();
boolean rootParser = parserClass.equals(myGrammarRootParser);
Set<String> imports = new LinkedHashSet<>();
imports.addAll(Arrays.asList(PSI_BUILDER_CLASS,
PSI_BUILDER_CLASS +".Marker",
"static " + elementTypeHolderClass + ".*",
"static " + myParserUtilClass + ".*"));
if (!rootParser) {
imports.add("static " + myGrammarRootParser + ".*");
}
else {
imports.addAll(Arrays.asList(IELEMENTTYPE_CLASS,
AST_NODE_CLASS,
TOKEN_SET_CLASS,
PSI_PARSER_CLASS,
LIGHT_PSI_PARSER_CLASS));
}
imports.addAll(parserImports);
generateClassHeader(parserClass, imports,
"@SuppressWarnings({\"SimplifiableIfStatement\", \"UnusedAssignment\"})",
Java.CLASS, "",
rootParser ? PSI_PARSER_CLASS : "",
rootParser ? LIGHT_PSI_PARSER_CLASS : "");
if (rootParser) {
generateRootParserContent(ownRuleNames);
}
for (String ruleName : ownRuleNames) {
BnfRule rule = ObjectUtils.assertNotNull(myFile.getRule(ruleName));
if (Rule.isExternal(rule) || Rule.isFake(rule)) continue;
if (myExpressionHelper.getExpressionInfo(rule) != null) continue;
out("/* ********************************************************** */");
generateNode(rule, rule.getExpression(), getFuncName(rule), new THashSet<>());
newLine();
}
for (String ruleName : ownRuleNames) {
BnfRule rule = myFile.getRule(ruleName);
ExpressionHelper.ExpressionInfo info = myExpressionHelper.getExpressionInfo(rule);
if (info != null && info.rootRule == rule) {
out("/* ********************************************************** */");
ExpressionGeneratorHelper.generateExpressionRoot(info, this);
newLine();
}
}
Map<String, String> reversedLambdas = new THashMap<>();
for (Map.Entry<String, String> e : myParserLambdas.entrySet()) {
String body = e.getValue();
if (body.startsWith("#")) {
String name = e.getKey();
String value = reversedLambdas.get(body);
if (value == null) {
value = wrapCallWithParserInstance(body.substring(1));
reversedLambdas.put(body, name);
}
out("final static Parser " + name + " = " + value + ";");
e.setValue(StringUtil.getShortName(parserClass) + "." + name);
}
}
out("}");
}
public String wrapCallWithParserInstance(String nodeCall) {
return format("new Parser() {\npublic boolean parse(PsiBuilder %s, int %s) {\nreturn %s;\n}\n}", N.builder, N.level, nodeCall);
}
private void generateRootParserContent(Set<String> ownRuleNames) {
List<Set<String>> extendsSet = buildExtendsSet(myGraphHelper.getRuleExtendsMap());
boolean generateExtendsSets = !extendsSet.isEmpty();
out("public ASTNode parse(IElementType %s, PsiBuilder %s) {", N.root, N.builder);
out("parseLight(%s, %s);", N.root, N.builder);
out("return %s.getTreeBuilt();", N.builder);
out("}");
newLine();
out("public void parseLight(IElementType %s, PsiBuilder %s) {", N.root, N.builder);
out("boolean %s;", N.result);
out("%s = adapt_builder_(%s, %s, this, %s);", N.builder, N.root, N.builder, generateExtendsSets ? "EXTENDS_SETS_" : null);
out("Marker %s = enter_section_(%s, 0, _COLLAPSE_, null);", N.marker, N.builder);
boolean first = true;
for (String ruleName : ownRuleNames) {
BnfRule rule = ObjectUtils.assertNotNull(myFile.getRule(ruleName));
if (getAttribute(rule, KnownAttribute.ELEMENT_TYPE) != null) continue;
if (!RuleGraphHelper.hasElementType(rule)) continue;
if (Rule.isFake(rule) || Rule.isMeta(rule)) continue;
if (G.generateRootRules != null && !G.generateRootRules.matcher(ruleName).matches()) continue;
ExpressionHelper.ExpressionInfo info = myExpressionHelper.getExpressionInfo(rule);
if (info != null && info.rootRule != rule) continue;
String elementType = getElementType(rule);
out("%sif (%s == %s) {", first ? "" : "else ", N.root, elementType);
String nodeCall = generateNodeCall(rule, null, getFuncName(rule));
out("%s = %s;", N.result, nodeCall.replace(format("%s + 1", N.level), "0"));
out("}");
if (first) first = false;
}
{
if (!first) out("else {");
out("%s = parse_root_(%s, %s, 0);", N.result, N.root, N.builder);
if (!first) out("}");
}
out("exit_section_(%s, 0, %s, %s, %s, true, TRUE_CONDITION);", N.builder, N.marker, N.root, N.result);
out("}");
newLine();
{
BnfRule rootRule = myFile.getRule(myGrammarRoot);
String nodeCall = generateNodeCall(rootRule, null, myGrammarRoot);
out("protected boolean parse_root_(IElementType %s, PsiBuilder %s, int %s) {", N.root, N.builder, N.level);
out("return %s;", nodeCall);
out("}");
newLine();
}
if (generateExtendsSets) {
out("public static final TokenSet[] EXTENDS_SETS_ = new TokenSet[] {");
StringBuilder sb = new StringBuilder();
for (Set<String> elementTypes : extendsSet) {
int i = 0;
for (String elementType : elementTypes) {
if (i > 0) sb.append(i % 4 == 0 ? ",\n" : ", ");
sb.append(elementType);
i++;
}
out("create_token_set_(%s),", sb);
sb.setLength(0);
}
out("};");
newLine();
}
}
@NotNull
private List<Set<String>> buildExtendsSet(@NotNull MultiMap<BnfRule, BnfRule> map) {
if (map.isEmpty()) return Collections.emptyList();
List<Set<String>> result = ContainerUtil.newArrayList();
for (Map.Entry<BnfRule, Collection<BnfRule>> entry : map.entrySet()) {
Set<String> set = null;
for (BnfRule rule : entry.getValue()) {
if (!RuleGraphHelper.hasElementType(rule)) continue;
String elementType = Rule.isFake(rule) && !myFakeRulesWithType.contains(rule.getName())
|| getSynonymTargetOrSelf(rule) != rule ? null : getElementType(rule);
if (StringUtil.isEmpty(elementType)) continue;
if (set == null) set = ContainerUtil.newTreeSet();
set.add(elementType);
}
if (set != null && set.size() > 1) result.add(set);
}
result.sort(Comparator.comparingInt(Set::size));
for (ListIterator<Set<String>> it = result.listIterator(); it.hasNext(); ) {
Set<String> smaller = it.next();
for (Set<String> bigger : result.subList(it.nextIndex(), result.size())) {
if (bigger.containsAll(smaller)) {
it.remove();
break;
}
}
}
return result;
}
private enum Java { CLASS, INTERFACE, ABSTRACT_CLASS }
private void generateClassHeader(String className,
Collection<String> imports,
String annos,
Java javaType,
String... supers) {
generateFileHeader(className);
String packageName = StringUtil.getPackageName(className);
String shortClassName = StringUtil.getShortName(className);
out("package %s;", packageName);
newLine();
Set<String> realImports = ContainerUtil.newLinkedHashSet(packageName + ".*");
Function<String, String> shortener = newClassNameShortener(realImports);
for (String item : imports) {
for (String s : StringUtil.tokenize(item.replaceAll("\\s+", " "), TYPE_TEXT_SEPARATORS)) {
s = StringUtil.trimStart(StringUtil.trimStart(s, "? super "), "? extends ");
if (!s.contains(".") || !s.equals(shortener.fun(s))) continue;
if (myPackageClasses.contains(StringUtil.getShortName(s))) continue;
realImports.add(s);
out("import %s;", s);
}
}
newLine();
StringBuilder sb = new StringBuilder();
for (int i = 0, supersLength = supers.length; i < supersLength; i++) {
String aSuper = supers[i];
if (StringUtil.isEmpty(aSuper)) continue;
if (imports.contains(aSuper + ";")) {
aSuper = StringUtil.getShortName(aSuper);
}
if (i == 0) {
sb.append(" extends ").append(shortener.fun(aSuper));
}
else if (javaType != Java.INTERFACE && i == 1) {
sb.append(" implements ").append(shortener.fun(aSuper));
}
else {
sb.append(", ").append(shortener.fun(aSuper));
}
}
if (StringUtil.isNotEmpty(annos)) {
out(annos);
}
out("public %s %s%s {", Case.LOWER.apply(javaType.name()).replace('_', ' '), shortClassName, sb.toString());
newLine();
myShortener = shortener;
}
@NotNull
private static Function<String, String> newClassNameShortener(final Set<String> realImports) {
return s -> {
boolean changed = false;
StringBuilder sb = new StringBuilder();
boolean vararg = s.endsWith("...");
for (String part : StringUtil.tokenize(new StringTokenizer(StringUtil.trimEnd(s, "..."), TYPE_TEXT_SEPARATORS, true))) {
String pkg;
String wildcard = part.startsWith("? super ") ? "? super " : part.startsWith("? extends ") ? "? extends " : "";
part = StringUtil.trimStart(part, wildcard);
if (TYPE_TEXT_SEPARATORS.contains(part)) {
sb.append(part).append(part.equals(",") ? " " : "");
}
else if (realImports.contains(part) ||
"java.lang".equals(pkg = StringUtil.getPackageName(part)) ||
realImports.contains(pkg + ".*")) {
sb.append(wildcard).append(StringUtil.getShortName(part));
changed = true;
}
else {
sb.append(wildcard).append(part);
}
}
return changed ? sb.append(vararg? "..." : "").toString() : s;
};
}
private void generateFileHeader(String className) {
String header = getRootAttribute(myFile, KnownAttribute.CLASS_HEADER, className);
String text = StringUtil.isEmpty(header) ? "" : getStringOrFile(header);
if (StringUtil.isNotEmpty(text)) {
out(text);
}
myOffset = 0;
}
private String getStringOrFile(String classHeader) {
try {
File file = new File(mySourcePath, classHeader);
if (file.exists()) return FileUtil.loadFile(file);
}
catch (IOException ex) {
LOG.error(ex);
}
return classHeader.startsWith("//") || classHeader.startsWith("/*")? classHeader :
StringUtil.countNewLines(classHeader) > 0 ? "/*\n" + classHeader + "\n*/" :
"// " + classHeader;
}
void generateNode(BnfRule rule, BnfExpression initialNode, String funcName, Set<BnfExpression> visited) {
boolean isRule = initialNode.getParent() == rule;
BnfExpression node = getNonTrivialNode(initialNode);
IElementType type = getEffectiveType(node);
for (String s : StringUtil.split((StringUtil.isEmpty(node.getText()) ? initialNode : node).getText(), "\n")) {
out("// " + s);
}
boolean firstNonTrivial = node == Rule.firstNotTrivial(rule);
boolean isPrivate = !(isRule || firstNonTrivial) || Rule.isPrivate(rule) || myGrammarRoot.equals(rule.getName());
boolean isLeft = firstNonTrivial && Rule.isLeft(rule);
boolean isLeftInner = isLeft && (isPrivate || Rule.isInner(rule));
boolean isUpper = !isPrivate && Rule.isUpper(rule);
String recoverWhile = !firstNonTrivial ? null : getAttribute(rule, KnownAttribute.RECOVER_WHILE);
Map<String, String> hooks = firstNonTrivial ? getAttribute(rule, KnownAttribute.HOOKS).asMap() : Collections.emptyMap();
final boolean canCollapse = !isPrivate && (!isLeft || isLeftInner) && firstNonTrivial && myGraphHelper.canCollapse(rule);
String elementType = getElementType(rule);
String elementTypeRef = !isPrivate && StringUtil.isNotEmpty(elementType) ? elementType : null;
final List<BnfExpression> children;
String extraArguments = collectExtraArguments(rule, node, true);
out("%sstatic boolean %s(PsiBuilder %s, int %s%s) {", !isRule ? "private " : isPrivate ? "" : "public ", funcName, N.builder, N.level, extraArguments);
if (node instanceof BnfReferenceOrToken || node instanceof BnfLiteralExpression || node instanceof BnfExternalExpression) {
children = Collections.singletonList(node);
if (isPrivate && !isLeftInner && recoverWhile == null) {
String nodeCall = generateNodeCall(rule, node, getNextName(funcName, 0));
out("return %s;", nodeCall);
out("}");
if (node instanceof BnfExternalExpression && ((BnfExternalExpression)node).getExpressionList().size() > 1) {
generateNodeChildren(rule, funcName, children, visited);
}
return;
}
else {
type = BNF_SEQUENCE;
}
}
else {
children = getChildExpressions(node);
//if (children.isEmpty() && recoverWhile == null) {
// if (!isPrivate && !StringUtil.isEmpty(elementType)) {
// if (isLeft || isLeftInner) {
// out("Marker %s = enter_section_(%s, %s, %s, %s);", N.marker, N.builder, N.level, isLeftInner ? "_LEFT_INNER_" : "_LEFT_", null);
// out("exit_section_(%s, %s, %s, %s, %s, %s, %s);", N.builder, N.level, N.marker, elementType, true, false, null);
// }
// else {
// out("Marker %s = enter_section_(%s);", N.marker, N.builder);
// out("exit_section_(%s, %s, %s, %s);", N.builder, N.marker, elementType, true);
// }
// }
// out("return true;");
// out("}");
// return;
//}
}
if (!children.isEmpty()) {
out("if (!recursion_guard_(%s, %s, \"%s\")) return false;", N.builder, N.level, funcName);
}
String frameName = !children.isEmpty() && firstNonTrivial && !Rule.isMeta(rule)? quote(getRuleDisplayName(rule, !isPrivate)) : null;
if (recoverWhile == null && (isRule || firstNonTrivial)) {
frameName = generateFirstCheck(rule, frameName, true);
}
PinMatcher pinMatcher = new PinMatcher(rule, type, firstNonTrivial ? rule.getName() : funcName);
boolean pinApplied = false;
final boolean alwaysTrue = children.isEmpty() || (type == BNF_OP_OPT || type == BNF_OP_ZEROMORE);
boolean pinned = pinMatcher.active() && pinMatcher.shouldGenerate(children);
if (!alwaysTrue) {
boolean value = type == BNF_OP_ZEROMORE || type == BNF_OP_OPT || children.isEmpty();
out("boolean %s%s%s;", N.result, children.isEmpty() || type == BNF_OP_ZEROMORE ? " = " + value : "",
pinned ? format(", %s", N.pinned) : "");
}
List<String> modifierList = ContainerUtil.newSmartList();
if (canCollapse) modifierList.add("_COLLAPSE_");
if (isLeftInner) modifierList.add("_LEFT_INNER_");
else if (isLeft) modifierList.add("_LEFT_");
if (type == BNF_OP_AND) modifierList.add("_AND_");
else if (type == BNF_OP_NOT) modifierList.add("_NOT_");
if (isUpper) modifierList.add("_UPPER_");
if (modifierList.isEmpty() && (pinned || frameName != null)) modifierList.add("_NONE_");
boolean sectionRequired = !alwaysTrue || !isPrivate || isLeft || recoverWhile != null;
boolean sectionRequiredSimple = sectionRequired && modifierList.isEmpty() && recoverWhile == null && frameName == null;
String modifiers = modifierList.isEmpty()? "_NONE_" : StringUtil.join(modifierList, " | ");
if (sectionRequiredSimple) {
out("Marker %s = enter_section_(%s);", N.marker, N.builder);
}
else if (sectionRequired) {
boolean shortVersion = frameName == null && elementTypeRef == null;
if (shortVersion) {
out("Marker %s = enter_section_(%s, %s, %s);", N.marker, N.builder, N.level, modifiers);
}
else {
out("Marker %s = enter_section_(%s, %s, %s, %s, %s);", N.marker, N.builder, N.level, modifiers, elementTypeRef, frameName);
}
}
boolean predicateEncountered = false;
int[] skip = {0};
for (int i = 0, p = 0, childrenSize = children.size(); i < childrenSize; i++) {
BnfExpression child = children.get(i);
String nodeCall = generateNodeCall(rule, child, getNextName(funcName, i));
if (type == BNF_CHOICE) {
out("%s%s = %s;", i > 0 ? format("if (!%s) ", N.result) : "", N.result, nodeCall);
}
else if (type == BNF_SEQUENCE) {
predicateEncountered |= pinApplied && getEffectiveExpression(myFile, child) instanceof BnfPredicate;
if (skip[0] == 0) {
ConsumeType consumeType = getEffectiveConsumeType(rule, node, null);
nodeCall = generateTokenSequenceCall(children, i, pinMatcher, pinApplied, skip, nodeCall, false, consumeType);
if (i == 0) {
out("%s = %s;", N.result, nodeCall);
}
else {
if (pinApplied && G.generateExtendedPin && !predicateEncountered) {
if (i == childrenSize - 1) {
// do not report error for last child
if (i == p + 1) {
out("%s = %s && %s;", N.result, N.result, nodeCall);
}
else {
out("%s = %s && %s && %s;", N.result, N.pinned, nodeCall, N.result);
}
}
else if (i == p + 1) {
out("%s = %s && report_error_(%s, %s);", N.result, N.result, N.builder, nodeCall);
}
else {
out("%s = %s && report_error_(%s, %s) && %s;", N.result, N.pinned, N.builder, nodeCall, N.result);
}
}
else {
out("%s = %s && %s;", N.result, N.result, nodeCall);
}
}
}
else {
skip[0]--; // we are inside already generated token sequence
if (pinApplied && i == p + 1) p++; // shift pinned index as we skip
}
if (pinned && !pinApplied && pinMatcher.matches(i, child)) {
pinApplied = true;
p = i;
out("%s = %s; // pin = %s", N.pinned, N.result, pinMatcher.pinValue);
}
}
else if (type == BNF_OP_OPT) {
out(nodeCall + ";");
}
else if (type == BNF_OP_ONEMORE || type == BNF_OP_ZEROMORE) {
if (type == BNF_OP_ONEMORE) {
out("%s = %s;", N.result, nodeCall);
}
out("int %s = current_position_(%s);", N.pos, N.builder);
out("while (%s) {", alwaysTrue ? "true" : N.result);
out("if (!%s) break;", nodeCall);
out("if (!empty_element_parsed_guard_(%s, \"%s\", %s)) break;", N.builder, funcName, N.pos);
out("%s = current_position_(%s);", N.pos, N.builder);
out("}");
}
else if (type == BNF_OP_AND) {
out("%s = %s;", N.result, nodeCall);
}
else if (type == BNF_OP_NOT) {
out("%s = !%s;", N.result, nodeCall);
}
else {
addWarning(myFile.getProject(), "unexpected: " + type);
}
}
if (sectionRequired) {
String resultRef = alwaysTrue ? "true" : N.result;
if (!hooks.isEmpty()) {
for (Map.Entry<String, String> entry : hooks.entrySet()) {
String hookName = ParserGeneratorUtil.toIdentifier(entry.getKey(), null, Case.UPPER);
out("register_hook_(%s, %s, %s);", N.builder, hookName, entry.getValue());
}
}
if (sectionRequiredSimple) {
out("exit_section_(%s, %s, %s, %s);", N.builder, N.marker, elementTypeRef, resultRef);
}
else {
String pinnedRef = pinned ? N.pinned : "false";
String recoverCall;
if (recoverWhile != null) {
BnfRule predicateRule = myFile.getRule(recoverWhile);
if (RECOVER_AUTO.equals(recoverWhile)) {
recoverCall = generateAutoRecoverCall(rule);
}
else if (Rule.isMeta(rule) && GrammarUtil.isDoubleAngles(recoverWhile)) {
recoverCall = formatArgName(recoverWhile.substring(2, recoverWhile.length() - 2));
}
else {
recoverCall = predicateRule == null ? null : generateWrappedNodeCall(rule, null, predicateRule.getName());
}
}
else {
recoverCall = null;
}
out("exit_section_(%s, %s, %s, %s, %s, %s);", N.builder, N.level, N.marker, resultRef, pinnedRef, recoverCall);
}
}
out("return %s;", alwaysTrue ? "true" : N.result + (pinned ? format(" || %s", N.pinned) : ""));
out("}");
generateNodeChildren(rule, funcName, children, visited);
}
/** @noinspection StringEquality*/
private String generateAutoRecoverCall(BnfRule rule) {
BnfFirstNextAnalyzer analyzer = new BnfFirstNextAnalyzer().setPredicateLookAhead(true);
Set<BnfExpression> nextExprSet = analyzer.calcNext(rule).keySet();
Set<String> nextSet = analyzer.asStrings(nextExprSet);
List<String> tokenTypes = new ArrayList<>(nextSet.size());
for (String s : nextSet) {
if (myFile.getRule(s) != null) continue; // ignore left recursion
if (s == BnfFirstNextAnalyzer.MATCHES_EOF || s == BnfFirstNextAnalyzer.MATCHES_NOTHING) continue;
boolean unknown = s == BnfFirstNextAnalyzer.MATCHES_ANY;
String t = unknown ? null : firstToElementType(s);
if (t != null) {
tokenTypes.add(t);
}
else {
tokenTypes.clear();
addWarning(myFile.getProject(), rule.getName() + " #auto recovery generation failed: " + s);
break;
}
}
StringBuilder sb = new StringBuilder(format("!nextTokenIsFast(%s, ", N.builder));
appendTokenTypes(sb, tokenTypes);
sb.append(")");
String constantName = rule.getName() + "_auto_recover_";
myParserLambdas.put(constantName, "#" + sb.toString());
return constantName;
}
public String generateFirstCheck(BnfRule rule, String frameName, boolean skipIfOne) {
if (G.generateFirstCheck <= 0) return frameName;
BnfFirstNextAnalyzer analyzer = new BnfFirstNextAnalyzer().setPredicateLookAhead(true);
Set<String> firstSet = analyzer.asStrings(analyzer.calcFirst(rule));
List<String> firstElementTypes = new ArrayList<>(firstSet.size());
for (String s : firstSet) {
if (myFile.getRule(s) != null) continue; // ignore left recursion
@SuppressWarnings("StringEquality")
boolean unknown = s == BnfFirstNextAnalyzer.MATCHES_EOF || s == BnfFirstNextAnalyzer.MATCHES_ANY;
String t = unknown? null : firstToElementType(s);
if (t != null) {
firstElementTypes.add(t);
}
else {
firstElementTypes.clear();
break;
}
}
ConsumeType forcedConsumeType = ExpressionGeneratorHelper.fixForcedConsumeType(myExpressionHelper, rule, null, null);
ConsumeType consumeType = ObjectUtils.chooseNotNull(forcedConsumeType, ConsumeType.forRule(rule));
boolean fast = consumeType == ConsumeType.FAST || consumeType == ConsumeType.SMART;
// do not include frameName if FIRST is known and its size is 1
boolean dropFrameName = skipIfOne && firstElementTypes.size() == 1;
if (!firstElementTypes.isEmpty() && firstElementTypes.size() <= G.generateFirstCheck) {
StringBuilder sb = new StringBuilder("if (!");
sb.append(fast ? "nextTokenIs" + consumeType.getMethodSuffix() : "nextTokenIs").append("(").append(N.builder).append(", ");
if (!fast && !dropFrameName) sb.append(frameName != null ? frameName : "\"\"").append(", ");
appendTokenTypes(sb, firstElementTypes);
sb.append(")) return false;");
out(sb.toString());
}
return dropFrameName && StringUtil.isEmpty(getAttribute(rule, KnownAttribute.NAME))? null : frameName;
}
void generateNodeChildren(BnfRule rule, String funcName, List<BnfExpression> children, Set<BnfExpression> visited) {
for (int i = 0, len = children.size(); i < len; i++) {
generateNodeChild(rule, children.get(i), funcName, i, visited);
}
}
void generateNodeChild(BnfRule rule, BnfExpression child, String funcName, int index, Set<BnfExpression> visited) {
if (child instanceof BnfExternalExpression) {
// generate parameters
List<BnfExpression> expressions = ((BnfExternalExpression)child).getExpressionList();
for (int j = 1, expressionsSize = expressions.size(); j < expressionsSize; j++) {
BnfExpression expression = expressions.get(j);
if (expression instanceof BnfLiteralExpression || expression instanceof BnfReferenceOrToken) continue;
if (expression instanceof BnfExternalExpression) {
generateNodeChildren(rule, getNextName(funcName, j - 1), Collections.singletonList(expression), visited);
}
else {
newLine();
generateNode(rule, expression, getNextName(getNextName(funcName, index), j - 1), visited);
}
}
}
else if (GrammarUtil.isAtomicExpression(child) || isTokenSequence(rule, child)) {
// do not generate
}
else {
newLine();
generateNode(rule, child, getNextName(funcName, index), visited);
}
}
private String collectExtraArguments(BnfRule rule, @Nullable BnfExpression expression, boolean declaration) {
if (expression == null) return "";
List<String> params = GrammarUtil.collectExtraArguments(rule, expression);
if (params.isEmpty()) return "";
final StringBuilder sb = new StringBuilder();
for (String param : params) {
sb.append(", ").append(declaration ? "Parser " : "").
append(formatArgName(param.substring(2, param.length() - 2)));
}
return sb.toString();
}
private String formatArgName(String s) {
String argName = s.trim();
return N.argPrefix + (N.argPrefix.isEmpty() || "_".equals(N.argPrefix) ? argName : StringUtil.capitalize(argName));
}
@Nullable
private String firstToElementType(String first) {
if (first.startsWith("#") || first.startsWith("-") || first.startsWith("<<")) return null;
String value = GrammarUtil.unquote(first);
//noinspection StringEquality
if (first != value) {
String attributeName = getTokenName(value);
if (attributeName != null && !first.startsWith("\"")) {
return getElementType(attributeName);
}
return null;
}
return getElementType(first);
}
@Nullable
private String getTokenName(String value) {
return mySimpleTokens.get(value);
}
private String generateTokenSequenceCall(List<BnfExpression> children,
int startIndex,
PinMatcher pinMatcher,
boolean pinApplied,
int[] skip,
String nodeCall,
boolean rollbackOnFail,
ConsumeType consumeType) {
if (startIndex == children.size() - 1 || !isConsumeTokenCall(nodeCall)) return nodeCall;
List<String> list = ContainerUtil.newArrayList();
int pin = pinApplied ? -1 : 0;
for (int i = startIndex, len = children.size(); i < len; i++) {
BnfExpression child = children.get(i);
String text = child.getText();
String tokenName = child instanceof BnfStringLiteralExpression ? getTokenName(GrammarUtil.unquote(text)) :
child instanceof BnfReferenceOrToken && myFile.getRule(text) == null ? text : null;
if (tokenName == null) break;
list.add(getElementType(tokenName));
if (!pinApplied && pinMatcher.matches(i, child)) {
pin = i - startIndex + 1;
}
}
if (list.size() < 2) return nodeCall;
skip[0] = list.size() - 1;
String consumeMethodName = (rollbackOnFail ? "parseTokens" : "consumeTokens") + (consumeType == ConsumeType.SMART ? consumeType.getMethodSuffix() : "");
return format("%s(%s, %d, %s)", consumeMethodName, N.builder, pin, StringUtil.join(list, ", "));
}
private static boolean isConsumeTokenCall(String nodeCall) {
int idx = nodeCall.indexOf('(');
return idx > 0 && ConsumeType.forMethod(nodeCall.substring(0, idx)) != null;
}
String generateNodeCall(BnfRule rule, @Nullable BnfExpression node, String nextName) {
return generateNodeCall(rule, node, nextName, null);
}
String generateNodeCall(BnfRule rule, @Nullable BnfExpression node, String nextName, @Nullable ConsumeType forcedConsumeType) {
ConsumeType consumeType = getEffectiveConsumeType(rule, node, forcedConsumeType);
String consumeMethodName = KnownAttribute.CONSUME_TOKEN_METHOD.getDefaultValue() + consumeType.getMethodSuffix();
IElementType type = node == null ? BNF_REFERENCE_OR_TOKEN : getEffectiveType(node);
String text = node == null ? nextName : node.getText();
if (type == BNF_STRING) {
String value = GrammarUtil.unquote(text);
String attributeName = getTokenName(value);
if (attributeName != null) {
return generateConsumeToken(attributeName, consumeMethodName);
}
return generateConsumeTextToken(text.startsWith("\"") ? value : StringUtil.escapeStringCharacters(value), consumeMethodName);
}
else if (type == BNF_NUMBER) {
return generateConsumeTextToken(text, consumeMethodName);
}
else if (type == BNF_REFERENCE_OR_TOKEN) {
String value = GrammarUtil.stripQuotesAroundId(text);
BnfRule subRule = myFile.getRule(value);
if (subRule != null) {
String method;
if (Rule.isExternal(subRule)) {
StringBuilder clause = new StringBuilder();
method = generateExternalCall(rule, clause, GrammarUtil.getExternalRuleExpressions(subRule), nextName);
return format("%s(%s, %s + 1%s)", method, N.builder, N.level, clause);
}
else {
ExpressionHelper.ExpressionInfo info = ExpressionGeneratorHelper.getInfoForExpressionParsing(myExpressionHelper, subRule);
BnfRule rr = info != null ? info.rootRule : subRule;
method = getFuncName(rr);
String parserClass = myRuleParserClasses.get(rr.getName());
if (!parserClass.equals(myGrammarRootParser) && !parserClass.equals(myRuleParserClasses.get(rule.getName()))) {
method = StringUtil.getShortName(parserClass) + "." + method;
}
if (info == null) {
return format("%s(%s, %s + 1)", method, N.builder, N.level);
}
else {
return format("%s(%s, %s + 1, %d)", method, N.builder, N.level, info.getPriority(subRule) - 1);
}
}
}
// allow token usage by registered token name instead of token text
if (!mySimpleTokens.containsKey(text) && !mySimpleTokens.values().contains(text)) {
mySimpleTokens.put(text, null);
}
return generateConsumeToken(text, consumeMethodName);
}
else if (isTokenSequence(rule, node)) {
PinMatcher pinMatcher = new PinMatcher(rule, type, nextName);
List<BnfExpression> childExpressions = getChildExpressions(node);
PsiElement firstElement = ContainerUtil.getFirstItem(childExpressions);
String nodeCall = generateNodeCall(rule, (BnfExpression) firstElement, getNextName(nextName, 0), consumeType);
for (PsiElement e : childExpressions) {
String t = e instanceof BnfStringLiteralExpression ? GrammarUtil.unquote(e.getText()) : e.getText();
if (!mySimpleTokens.containsKey(t) && !mySimpleTokens.values().contains(t)) {
mySimpleTokens.put(t, null);
}
}
return generateTokenSequenceCall(childExpressions, 0, pinMatcher, false, new int[]{0}, nodeCall, true, consumeType);
}
else if (type == BNF_EXTERNAL_EXPRESSION) {
List<BnfExpression> expressions = ((BnfExternalExpression)node).getExpressionList();
if (expressions.size() == 1 && Rule.isMeta(rule)) {
return format("%s.parse(%s, %s)", formatArgName(expressions.get(0).getText()), N.builder, N.level);
}
else {
StringBuilder clause = new StringBuilder();
String method = generateExternalCall(rule, clause, expressions, nextName);
return format("%s(%s, %s + 1%s)", method, N.builder, N.level, clause);
}
}
else {
String extraArguments = collectExtraArguments(rule, node, false);
return format("%s(%s, %s + 1%s)", nextName, N.builder, N.level, extraArguments);
}
}
@NotNull
private ConsumeType getEffectiveConsumeType(@NotNull BnfRule rule, @Nullable BnfExpression node, @Nullable ConsumeType forcedConsumeType) {
if (forcedConsumeType == ConsumeType.DEFAULT) return ConsumeType.DEFAULT;
PsiElement parent = node == null ? null : node.getParent();
if (forcedConsumeType == null && parent instanceof BnfSequence && parent.getFirstChild() != node) return ConsumeType.DEFAULT;
ConsumeType fixed = ExpressionGeneratorHelper.fixForcedConsumeType(myExpressionHelper, rule, node, forcedConsumeType);
return fixed != null ? fixed : ConsumeType.forRule(rule);
}
private String generateExternalCall(BnfRule rule, StringBuilder clause, List<BnfExpression> expressions, String nextName) {
List<BnfExpression> callParameters = expressions;
List<BnfExpression> metaParameters = Collections.emptyList();
List<String> metaParameterNames = Collections.emptyList();
String method = expressions.size() > 0 ? expressions.get(0).getText() : null;
BnfRule targetRule = method == null ? null : myFile.getRule(method);
// handle external rule call: substitute and merge arguments from external expression and rule definition
if (targetRule != null) {
if (Rule.isExternal(targetRule)) {
metaParameterNames = GrammarUtil.collectExtraArguments(targetRule, targetRule.getExpression());
callParameters = GrammarUtil.getExternalRuleExpressions(targetRule);
metaParameters = expressions;
method = callParameters.get(0).getText();
if (metaParameterNames.size() < expressions.size() - 1) {
callParameters = ContainerUtil.concat(callParameters, expressions.subList(metaParameterNames.size() + 1, expressions.size()));
}
}
else {
String parserClass = myRuleParserClasses.get(method);
if (!parserClass.equals(myGrammarRootParser) && !parserClass.equals(myRuleParserClasses.get(rule.getName()))) {
method = StringUtil.getShortName(parserClass) + "." + method;
}
}
}
if (callParameters.size() > 1) {
for (int i = 1, len = callParameters.size(); i < len; i++) {
clause.append(", ");
BnfExpression nested = callParameters.get(i);
String argument = nested.getText();
String argNextName;
int metaIdx;
if (argument.startsWith("<<") && (metaIdx = metaParameterNames.indexOf(argument)) > -1) {
nested = metaParameters.get(metaIdx + 1);
argument = nested.getText();
argNextName = getNextName(nextName, metaIdx);
}
else {
argNextName = getNextName(nextName, i - 1);
}
if (nested instanceof BnfReferenceOrToken) {
if (myFile.getRule(argument) != null) {
clause.append(generateWrappedNodeCall(rule, nested, argument));
}
else {
String tokenType = getElementType(argument);
clause.append(generateWrappedNodeCall(rule, nested, tokenType));
}
}
else if (nested instanceof BnfLiteralExpression) {
String attributeName = getTokenName(GrammarUtil.unquote(argument));
if (attributeName != null) {
clause.append(generateWrappedNodeCall(rule, nested, attributeName));
}
else if (argument.startsWith("\'")) {
clause.append(GrammarUtil.unquote(argument));
}
else {
clause.append(argument);
}
}
else if (nested instanceof BnfExternalExpression) {
List<BnfExpression> expressionList = ((BnfExternalExpression)nested).getExpressionList();
boolean metaRule = Rule.isMeta(rule);
if (metaRule && expressionList.size() == 1) {
clause.append(formatArgName(expressionList.get(0).getText()));
}
else {
clause.append(generateWrappedNodeCall(rule, nested, argNextName));
}
}
else {
clause.append(generateWrappedNodeCall(rule, nested, argNextName));
}
}
}
return method;
}
private String generateWrappedNodeCall(BnfRule rule, @Nullable BnfExpression nested, final String nextName) {
if (!collectExtraArguments(rule, nested, false).isEmpty()) {
return wrapCallWithParserInstance(generateNodeCall(rule, nested, nextName));
}
String constantName = toIdentifier(nextName, null, Case.AS_IS) + "_parser_";
String current = myParserLambdas.get(constantName);
if (current == null) {
myParserLambdas.put(constantName, "#" + generateNodeCall(rule, nested, nextName));
return constantName;
}
else if (current.startsWith("#")) {
return constantName;
}
else {
return current;
}
}
private String generateConsumeToken(String tokenName, String consumeMethodName) {
myTokensUsedInGrammar.add(tokenName);
return format("%s(%s, %s)", consumeMethodName, N.builder, getElementType(tokenName));
}
public String generateConsumeTextToken(String tokenText, String consumeMethodName) {
return format("%s(%s, \"%s\")", consumeMethodName, N.builder, tokenText);
}
private String getElementType(String token) {
return ParserGeneratorUtil.getTokenType(myFile, token, G.generateTokenCase);
}
String getElementType(BnfRule r) {
return ParserGeneratorUtil.getElementType(r, G.generateElementCase);
}
/*ElementTypes******************************************************************/
private void generateElementTypesHolder(String className, Map<String, BnfRule> sortedCompositeTypes) {
String implPackage = getPsiImplPackage(myFile);
String tokenTypeClass = getRootAttribute(myFile, KnownAttribute.TOKEN_TYPE_CLASS);
String tokenTypeFactory = getRootAttribute(myFile, KnownAttribute.TOKEN_TYPE_FACTORY);
Set<String> imports = new LinkedHashSet<>();
imports.add(IELEMENTTYPE_CLASS);
if (G.generatePsi) {
imports.add(PSI_ELEMENT_CLASS);
imports.add(AST_NODE_CLASS);
}
Map<String, Pair<String, String>> compositeToClassAndFactoryMap = new THashMap<>();
for (String elementType : sortedCompositeTypes.keySet()) {
BnfRule rule = sortedCompositeTypes.get(elementType);
String elementTypeClass = getAttribute(rule, KnownAttribute.ELEMENT_TYPE_CLASS);
String elementTypeFactory = getAttribute(rule, KnownAttribute.ELEMENT_TYPE_FACTORY);
compositeToClassAndFactoryMap.put(elementType, Pair.create(elementTypeClass, elementTypeFactory));
if (elementTypeFactory != null) {
imports.add(StringUtil.getPackageName(elementTypeFactory));
}
else {
ContainerUtil.addIfNotNull(imports, elementTypeClass);
}
}
if (tokenTypeFactory != null) {
imports.add(StringUtil.getPackageName(tokenTypeFactory));
}
else {
ContainerUtil.addIfNotNull(imports, tokenTypeClass);
}
if (G.generatePsi) {
imports.add(implPackage + ".*");
if (G.generatePsiClassesMap) {
imports.add(CommonClassNames.JAVA_UTIL_COLLECTIONS);
imports.add(CommonClassNames.JAVA_UTIL_SET);
imports.add("java.util.LinkedHashMap");
}
}
generateClassHeader(className, imports, "", Java.INTERFACE);
if (G.generateElementTypes) {
for (String elementType : sortedCompositeTypes.keySet()) {
Pair<String, String> pair = compositeToClassAndFactoryMap.get(elementType);
String elementCreateCall;
if (pair.second == null) {
elementCreateCall = "new " + StringUtil.getShortName(pair.first);
}
else {
elementCreateCall = myShortener.fun(StringUtil.getPackageName(pair.second)) + "." + StringUtil.getShortName(pair.second);
}
String callFix = elementCreateCall.equals("new IElementType") ? ", null" : "";
out("IElementType " + elementType + " = " + elementCreateCall + "(\"" + elementType + "\"" + callFix + ");");
}
}
if (G.generateTokenTypes) {
newLine();
Map<String, String> sortedTokens = ContainerUtil.newTreeMap();
String tokenCreateCall;
if (tokenTypeFactory == null) {
tokenCreateCall = "new " + StringUtil.getShortName(tokenTypeClass);
}
else {
tokenCreateCall = myShortener.fun(StringUtil.getPackageName(tokenTypeFactory)) + "." + StringUtil.getShortName(tokenTypeFactory);
}
for (String tokenText : mySimpleTokens.keySet()) {
String tokenName = ObjectUtils.chooseNotNull(mySimpleTokens.get(tokenText), tokenText);
if (isIgnoredWhitespaceToken(tokenName, tokenText)) continue;
sortedTokens.put(getElementType(tokenName), isRegexpToken(tokenText) ? tokenName : tokenText);
}
for (String tokenType : sortedTokens.keySet()) {
String callFix = tokenCreateCall.equals("new IElementType") ? ", null" : "";
String tokenString = sortedTokens.get(tokenType);
out("IElementType " + tokenType + " = " + tokenCreateCall + "(\"" + StringUtil.escapeStringCharacters(tokenString) + "\""+callFix+");");
}
}
if (G.generatePsi && G.generatePsiClassesMap) {
newLine();
out("class Classes {");
out("public static Class<?> findClass(IElementType elementType) {");
out("return ourMap.get(elementType);");
out("}");
newLine();
out("public static %s<IElementType> elementTypes() {", myShortener.fun(CommonClassNames.JAVA_UTIL_SET));
out("return %s.unmodifiableSet(ourMap.keySet());", myShortener.fun(CommonClassNames.JAVA_UTIL_COLLECTIONS));
out("}");
newLine();
String type = myShortener.fun("java.util.LinkedHashMap") + "<IElementType, Class<?>>";
out("private static final %s ourMap = new %1$s();", type);
newLine();
out("static {");
for (String elementType : sortedCompositeTypes.keySet()) {
BnfRule rule = sortedCompositeTypes.get(elementType);
if (myAbstractRules.contains(rule.getName())) continue;
String psiClass = getRulePsiClassName(rule, myPsiImplClassFormat);
out("ourMap.put(" + elementType + ", " + psiClass + ".class);");
}
out("}");
out("}");
}
if (G.generatePsi && G.generatePsiFactory) {
newLine();
out("class Factory {");
out("public static " + myShortener.fun(PSI_ELEMENT_CLASS) + " createElement(" + myShortener.fun(AST_NODE_CLASS) + " node) {");
out("IElementType type = node.getElementType();");
boolean first = true;
for (String elementType : sortedCompositeTypes.keySet()) {
BnfRule rule = sortedCompositeTypes.get(elementType);
if (myAbstractRules.contains(rule.getName())) continue;
String psiClass = getRulePsiClassName(rule, myPsiImplClassFormat);
out((!first ? "else" : "") + " if (type == " + elementType + ") {");
out("return new " + psiClass + "(node);");
first = false;
out("}");
}
out("throw new AssertionError(\"Unknown element type: \" + type);");
out("}");
out("}");
}
out("}");
}
private boolean isIgnoredWhitespaceToken(@NotNull String tokenName, @NotNull String tokenText) {
return isRegexpToken(tokenText) &&
!myTokensUsedInGrammar.contains(tokenName) &&
matchesAny(getRegexpTokenRegexp(tokenText), " ", "\n") &&
!matchesAny(getRegexpTokenRegexp(tokenText), "a", "1", "_", ".");
}
/*PSI******************************************************************/
private void generatePsiIntf(BnfRule rule,
String psiClass,
Collection<String> psiSupers) {
String stubClass = getAttribute(rule, KnownAttribute.STUB_CLASS);
if (StringUtil.isNotEmpty(stubClass)) {
psiSupers = new LinkedHashSet<>(psiSupers);
psiSupers.add(STUB_BASED_PSI_ELEMENT + "<" + stubClass + ">");
}
Set<String> imports = ContainerUtil.newLinkedHashSet();
imports.addAll(Arrays.asList("java.util.List",
"org.jetbrains.annotations.*",
PSI_ELEMENT_CLASS));
imports.addAll(psiSupers);
imports.addAll(getRuleMethodTypesToImport(rule));
generateClassHeader(psiClass, imports, "", Java.INTERFACE, ArrayUtil.toStringArray(psiSupers));
generatePsiClassMethods(rule, true);
out("}");
}
private void generatePsiImpl(BnfRule rule,
String psiClass,
String superInterface,
Map<String, String> realSuperClasses) {
String typeHolderClass = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS);
String stubName = myRulesStubNames.get(rule.getName());
String implSuper = realSuperClasses.get(rule.getName());
Set<String> imports = ContainerUtil.newLinkedHashSet();
imports.addAll(Arrays.asList(CommonClassNames.JAVA_UTIL_LIST,
"org.jetbrains.annotations.*",
AST_NODE_CLASS,
PSI_ELEMENT_CLASS));
if (myVisitorClassName != null) imports.add(PSI_ELEMENT_VISITOR_CLASS);
imports.add(myPsiTreeUtilClass);
imports.add("static " + typeHolderClass + ".*");
if (StringUtil.isNotEmpty(implSuper)) imports.add(implSuper);
imports.add(StringUtil.getPackageName(superInterface) + ".*");
imports.add(StringUtil.notNullize(myPsiImplUtilClass));
imports.addAll(getRuleMethodTypesToImport(rule));
Function<String, String> substitutor = stubName != null ? Functions.constant(stubName) : Function.ID;
List<NavigatablePsiElement> constructors = Collections.emptyList();
BnfRule topSuperRule = null;
for (BnfRule next = rule; next != null && next != topSuperRule; ) {
topSuperRule = next;
String superClass = realSuperClasses.get(next.getName());
if (superClass == null) continue;
next = getTopSuperRule(myFile, next);
if (next != null && next != topSuperRule && getAttribute(topSuperRule, KnownAttribute.MIXIN) == null) continue;
constructors = myJavaHelper.findClassMethods(getRawClassName(superClass), JavaHelper.MethodType.CONSTRUCTOR, "*", -1);
if (!constructors.isEmpty()) break;
}
for (NavigatablePsiElement m : constructors) {
collectMethodTypesToImport(Collections.singletonList(m), false, imports);
}
if (stubName != null && constructors.isEmpty()) imports.add(ISTUBELEMENTTYPE_CLASS);
if (stubName != null) imports.add(stubName);
if (!G.generateTokenTypes) {
// add parser static imports hoping external token constants are there
for (RuleMethodsHelper.MethodInfo info : myRulesMethodsHelper.getFor(rule)) {
if (info.rule == null && !StringUtil.isEmpty(info.name)) {
for (String s : getRootAttribute(myFile, KnownAttribute.PARSER_IMPORTS).asStrings()) {
if (s.startsWith("static ")) imports.add(s);
}
break;
}
}
}
Java javaType = myAbstractRules.contains(rule.getName()) ? Java.ABSTRACT_CLASS : Java.CLASS;
generateClassHeader(psiClass, imports, "", javaType, implSuper, superInterface);
String shortName = StringUtil.getShortName(psiClass);
if (constructors.isEmpty()) {
out("public " + shortName + "(" + myShortener.fun(AST_NODE_CLASS) + " node) {");
out("super(node);");
out("}");
newLine();
if (stubName != null) {
out("public " + shortName + "(" + myShortener.fun(stubName) + " stub, " + myShortener.fun(ISTUBELEMENTTYPE_CLASS) + " stubType) {");
out("super(stub, stubType);");
out("}");
newLine();
}
}
else {
for (NavigatablePsiElement m : constructors) {
List<String> types = myJavaHelper.getMethodTypes(m);
out("public " + shortName + "(" + getParametersString(types, 1, 3, substitutor, myShortener) + ") {");
out("super(" + getParametersString(types, 1, 2, substitutor, myShortener) + ");");
out("}");
newLine();
}
}
if (myVisitorClassName != null) {
String r = G.visitorValue != null ? "<" + G.visitorValue + ">" : "";
String t = G.visitorValue != null ? " " + G.visitorValue : "void";
String ret = G.visitorValue != null ? "return " : "";
out("public " + r + t + " accept(@NotNull " + myVisitorClassName + r + " visitor) {");
out(ret + "visitor.visit" + getRulePsiClassName(rule, null) + "(this);");
out("}");
newLine();
//if (topSuperRule == rule) {
out("public void accept(@NotNull " + myShortener.fun(PSI_ELEMENT_VISITOR_CLASS) + " visitor) {");
out("if (visitor instanceof " + myVisitorClassName + ") accept((" + myVisitorClassName + ")visitor);");
out("else super.accept(visitor);");
out("}");
newLine();
//}
}
generatePsiClassMethods(rule, false);
out("}");
}
private void generatePsiClassMethods(BnfRule rule, boolean intf) {
Set<String> visited = ContainerUtil.newTreeSet();
Set<String> ruleClasses = getRuleClasses(rule);
boolean mixedAST = JBIterable.from(ruleClasses)
.flatMap(s -> JBIterable.generate(s, myJavaHelper::getSuperClassName))
.find(BnfConstants.COMPOSITE_PSI_ELEMENT_CLASS::equals) != null;
for (RuleMethodsHelper.MethodInfo methodInfo : myRulesMethodsHelper.getFor(rule)) {
if (StringUtil.isEmpty(methodInfo.name)) continue;
switch (methodInfo.type) {
case RULE:
case TOKEN:
generatePsiAccessor(rule, methodInfo, intf, mixedAST);
break;
case USER:
generateUserPsiAccessors(rule, methodInfo, intf, mixedAST);
break;
case MIXIN:
boolean found = false;
if (intf) {
String mixinClass = getAttribute(rule, KnownAttribute.MIXIN);
List<NavigatablePsiElement> methods = myJavaHelper.findClassMethods(mixinClass, JavaHelper.MethodType.INSTANCE, methodInfo.name, -1);
for (NavigatablePsiElement method : methods) {
generateUtilMethod(methodInfo.name, method, true, false, visited);
found = true;
}
}
List<NavigatablePsiElement> methods = findRuleImplMethods(myJavaHelper, myPsiImplUtilClass, methodInfo.name, rule);
for (NavigatablePsiElement method : methods) {
generateUtilMethod(methodInfo.name, method, intf, true, visited);
found = true;
}
if (intf && !found) {
String ruleClassName = myShortener.fun(ContainerUtil.getFirstItem(ruleClasses));
String implClassName = StringUtil.getShortName(String.valueOf(myPsiImplUtilClass));
out("" +
"//WARNING: %s(...) is skipped\n" +
"//matching %s(%s, ...)\n" +
"//methods are not found in %s",
methodInfo.name, methodInfo.name, ruleClassName, implClassName);
newLine();
addWarning(myFile.getProject(), "%s.%s(%s, ...) method not found", implClassName, methodInfo.name, ruleClassName);
}
break;
default: throw new AssertionError(methodInfo.toString());
}
}
}
public Collection<String> getRuleMethodTypesToImport(BnfRule rule) {
Set<String> result = ContainerUtil.newTreeSet();
Collection<RuleMethodsHelper.MethodInfo> methods = myRulesMethodsHelper.getFor(rule);
for (RuleMethodsHelper.MethodInfo methodInfo : methods) {
if (methodInfo.rule != null) {
result.add(getAccessorType(methodInfo.rule));
}
}
String mixinClass = getAttribute(rule, KnownAttribute.MIXIN);
for (RuleMethodsHelper.MethodInfo methodInfo : methods) {
if (methodInfo.type != RuleMethodsHelper.MethodType.MIXIN) continue;
List<NavigatablePsiElement> mixinMethods = myJavaHelper.findClassMethods(mixinClass, JavaHelper.MethodType.INSTANCE, methodInfo.name, -1);
List<NavigatablePsiElement> implMethods = findRuleImplMethods(myJavaHelper, myPsiImplUtilClass, methodInfo.name, rule);
collectMethodTypesToImport(mixinMethods, false, result);
collectMethodTypesToImport(implMethods, true, result);
}
return result;
}
private void collectMethodTypesToImport(List<NavigatablePsiElement> methods, boolean isInPsiUtil, Set<String> result) {
for (NavigatablePsiElement method : methods) {
int count = 0;
List<String> types = myJavaHelper.getMethodTypes(method);
for (String s : types) {
if (count++ == 1 && isInPsiUtil) continue;
if (s.contains(".")) result.add(s);
}
for (String s : myJavaHelper.getAnnotations(method)) {
if (s.startsWith("kotlin.")) continue;
result.add(s);
}
}
}
private void generatePsiAccessor(BnfRule rule, RuleMethodsHelper.MethodInfo methodInfo, boolean intf, boolean mixedAST) {
RuleGraphHelper.Cardinality type = methodInfo.cardinality;
boolean isToken = methodInfo.rule == null;
boolean many = type.many();
String getterName = methodInfo.generateGetterName();
if (!intf) out("@Override");
if (type == REQUIRED) {
out("@NotNull");
}
else if (type == OPTIONAL) {
out("@Nullable");
}
else {
out("@NotNull");
}
String className = myShortener.fun(isToken ? PSI_ELEMENT_CLASS : getAccessorType(methodInfo.rule));
String tail = intf ? "();" : "() {";
out((intf ? "" : "public ") + (many ? myShortener.fun(CommonClassNames.JAVA_UTIL_LIST) + "<" : "") + className + (many ? "> " : " ") + getterName + tail);
if (!intf) {
out("return " + generatePsiAccessorImplCall(rule, methodInfo, mixedAST) + ";");
out("}");
}
newLine();
}
private String generatePsiAccessorImplCall(@NotNull BnfRule rule, @NotNull RuleMethodsHelper.MethodInfo methodInfo, boolean mixedAST) {
boolean isToken = methodInfo.rule == null;
RuleGraphHelper.Cardinality type = methodInfo.cardinality;
boolean many = type.many();
boolean required = type == REQUIRED && !many;
boolean stubbed = !isToken &&
myRulesStubNames.get(rule.getName()) != null &&
myRulesStubNames.get(methodInfo.rule.getName()) != null;
String result;
// todo REMOVEME. Keep old generation logic for a while.
if (!mixedAST && myRulesStubNames.isEmpty()) {
if (isToken) {
return (type == REQUIRED ? "findNotNullChildByType" : "findChildByType") +
"(" + getElementType(methodInfo.path) + ")";
}
else {
String className = myShortener.fun(getAccessorType(methodInfo.rule));
return many ? String.format("%s.getChildrenOfTypeAsList(this, %s.class)", myShortener.fun(myPsiTreeUtilClass), className) :
(type == REQUIRED ? "findNotNullChildByClass" : "findChildByClass") + "(" + className + ".class)";
}
}
// new logic
if (isToken) {
String getterName = mixedAST ? "findPsiChildByType" : "findChildByType";
result = getterName + "(" + getElementType(methodInfo.path) + ")";
}
else {
String className = myShortener.fun(getAccessorType(methodInfo.rule));
String getterName = stubbed && many ? "getStubChildrenOfTypeAsList" :
stubbed ? "getStubChildOfType" :
many ? "getChildrenOfTypeAsList" : "getChildOfType";
result = String.format("%s.%s(this, %s.class)", myShortener.fun(myPsiTreeUtilClass), getterName, className);
}
return required && !mixedAST ? "notNullChild(" + result + ")" : result;
}
private String getAccessorType(@NotNull BnfRule rule) {
if (Rule.isExternal(rule)) {
Pair<String, String> first = ContainerUtil.getFirstItem(getAttribute(rule, KnownAttribute.IMPLEMENTS));
return ObjectUtils.assertNotNull(first).second;
}
else {
return getRulePsiClassName(rule, myPsiClassFormat);
}
}
private void generateUserPsiAccessors(BnfRule startRule, RuleMethodsHelper.MethodInfo methodInfo, boolean intf, boolean mixedAST) {
StringBuilder sb = new StringBuilder();
BnfRule targetRule = startRule;
RuleGraphHelper.Cardinality cardinality = REQUIRED;
String context = "";
String[] splitPath = methodInfo.path.split("/");
boolean totalNullable = false;
for (int i = 0, count = 1; i < splitPath.length; i++) {
String pathElement = splitPath[i];
boolean last = i == splitPath.length - 1;
int indexStart = pathElement.indexOf('[');
int indexEnd = indexStart > 0 ? pathElement.lastIndexOf(']') : -1;
String item = indexEnd > -1 ? pathElement.substring(0, indexStart).trim() : pathElement.trim();
String index = indexEnd > -1 ? pathElement.substring(indexStart + 1, indexEnd).trim() : null;
if ("first".equals(index)) index = "0";
if (item.isEmpty()) continue;
RuleMethodsHelper.MethodInfo targetInfo = myRulesMethodsHelper.getMethodInfo(targetRule, item);
if (targetInfo == null ||
index != null && !targetInfo.cardinality.many() ||
i > 0 && StringUtil.isEmpty(targetInfo.name) && targetInfo.rule == null) {
if (intf) { // warn only once
addWarning(startRule.getProject(), "incorrect item '" + pathElement + "' in '" + startRule.getName() + "' method " +
methodInfo.name + "=\"" + methodInfo.path + "\"");
}
return; // missing rule, unknown or wrong cardinality
}
boolean many = targetInfo.cardinality.many();
String className = myShortener.fun(targetInfo.rule == null ? PSI_ELEMENT_CLASS : getAccessorType(targetInfo.rule));
String type = (many ? myShortener.fun(CommonClassNames.JAVA_UTIL_LIST) + "<" : "") + className + (many ? "> " : " ");
String curId = N.psiLocal + (count++);
if (!context.isEmpty()) {
if (cardinality.optional()) {
sb.append("if (").append(context).append(" == null) return null;\n");
}
context += ".";
}
if (last && index == null) {
sb.append("return ");
}
else {
sb.append(type).append(curId).append(" = ");
}
String targetCall;
if (StringUtil.isNotEmpty(targetInfo.name)) {
targetCall = targetInfo.generateGetterName() + "()";
}
else {
targetCall = generatePsiAccessorImplCall(startRule, targetInfo, mixedAST);
}
sb.append(context).append(targetCall).append(";\n");
context = curId;
targetRule = targetInfo.rule; // next accessors
cardinality = targetInfo.cardinality;
totalNullable |= cardinality.optional();
// list item
if (index != null) {
context += ".";
boolean isLast = index.equals("last");
if (isLast) index = context + "size() - 1";
curId = N.psiLocal + (count++);
if (last) {
sb.append("return ");
}
else {
sb.append(className).append(" ").append(curId).append(" = ");
}
if (cardinality != AT_LEAST_ONE || !index.equals("0")) { // range check
if (isLast) {
sb.append(context).append("isEmpty()? null : ");
}
else {
int val = StringUtil.parseInt(index, Integer.MAX_VALUE);
sb.append(context).append("size()").append(val == Integer.MAX_VALUE ? " - 1 < " + index : " < " + (val + 1))
.append(" ? null : ");
}
}
sb.append(context).append("get(").append(index).append(");\n");
context = curId;
cardinality = cardinality == AT_LEAST_ONE && index.equals("0") ? REQUIRED : OPTIONAL;
totalNullable |= cardinality.optional();
}
}
if (!intf) out("@Override");
if (!cardinality.many() && cardinality == REQUIRED && !totalNullable) {
out("@NotNull");
}
else {
out("@Nullable");
}
boolean many = cardinality.many();
String className = myShortener.fun(targetRule == null ? PSI_ELEMENT_CLASS : getAccessorType(targetRule));
String getterName = getGetterName(methodInfo.name);
String tail = intf ? "();" : "() {";
out((intf ? "" : "public ") + (many ? myShortener.fun(CommonClassNames.JAVA_UTIL_LIST) + "<" : "") + className + (many ? "> " : " ") + getterName + tail);
if (!intf) {
out(sb.toString());
out("}");
}
newLine();
}
private void generateUtilMethod(String methodName,
NavigatablePsiElement method,
boolean intf,
boolean isInPsiUtil,
Set<String> visited) {
List<String> methodTypes = method == null ? Collections.emptyList() : myJavaHelper.getMethodTypes(method);
String returnType = methodTypes.isEmpty()? "void" : myShortener.fun(methodTypes.get(0));
int offset = methodTypes.isEmpty() || isInPsiUtil && methodTypes.size() < 3 ? 0 :
isInPsiUtil ? 3 : 1;
if (!visited.add(methodName + methodTypes.subList(offset, methodTypes.size()))) return;
if (intf && methodTypes.size() == offset && "toString".equals(methodName)) return;
for (String s : myJavaHelper.getAnnotations(method)) {
if ("java.lang.Override".equals(s)) continue;
if (s.startsWith("kotlin.")) continue;
out("@" + myShortener.fun(s));
}
out("%s%s %s(%s)%s", intf ? "" : "public ", returnType, methodName,
getParametersString(methodTypes, offset, 3, Function.ID, myShortener),
intf ? ";" : " {");
if (!intf) {
String implUtilRef = myShortener.fun(StringUtil.notNullize(myPsiImplUtilClass, KnownAttribute.PSI_IMPL_UTIL_CLASS.getName()));
String string = getParametersString(methodTypes, offset, 2, Function.ID, myShortener);
out("%s%s.%s(this%s);", "void".equals(returnType) ? "" : "return ", implUtilRef, methodName,
string.isEmpty() ? "" : ", " + string);
out("}");
}
newLine();
}
}