src/org/intellij/grammar/generator/GeneratorBase.java (195 lines of code) (raw):

/* * Copyright 2011-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ package org.intellij.grammar.generator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.JBIterable; import org.intellij.grammar.KnownAttribute; import org.intellij.grammar.fleet.FleetBnfFileWrapper; import org.intellij.grammar.psi.BnfFile; import org.intellij.grammar.psi.BnfRule; import org.jetbrains.annotations.NotNull; 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.ParserGeneratorUtil.*; import static org.intellij.grammar.fleet.FleetConstants.FLEET_NAMESPACE; import static org.intellij.grammar.fleet.FleetConstants.FLEET_NAMESPACE_PREFIX; public abstract class GeneratorBase { public static final Logger LOG = Logger.getInstance(GeneratorBase.class); protected final BnfFile myFile; protected final Boolean myGenerateForFleet; private final String myOutputPath; private final String myPackagePrefix; protected final String mySourcePath; protected final String myGrammarRoot; protected final String myGrammarRootParser; protected final NameFormat myIntfClassFormat; protected final NameFormat myImplClassFormat; private int myOffset; private PrintWriter myOut; private NameShortener myShortener; protected final GenOptions G; protected NameShortener getShortener() { return myShortener; } protected enum Java {CLASS, INTERFACE, ABSTRACT_CLASS} protected GeneratorBase(@NotNull BnfFile psiFile, @NotNull String sourcePath, @NotNull String outputPath, @NotNull String packagePrefix) { myFile = psiFile; myGenerateForFleet = psiFile instanceof FleetBnfFileWrapper; G = new GenOptions(psiFile); mySourcePath = sourcePath; myOutputPath = outputPath; myPackagePrefix = packagePrefix; myIntfClassFormat = getPsiClassFormat(myFile); myImplClassFormat = getPsiImplClassFormat(myFile); List<BnfRule> rules = psiFile.getRules(); BnfRule rootRule = rules.isEmpty() ? null : rules.get(0); myGrammarRoot = rootRule == null ? null : rootRule.getName(); myGrammarRootParser = rootRule == null ? null : getRootAttribute(rootRule, KnownAttribute.PARSER_CLASS); } public abstract void generate() throws IOException; protected void generateClassHeader(String className, Set<String> imports, String annos, Java javaType, String... supers) { generateFileHeader(className); String packageName = generatePackageName(className); String shortClassName = StringUtil.getShortName(className); out("package %s;", packageName); newLine(); NameShortener shortener = new NameShortener(packageName, !G.generateFQN); Set<String> includedClasses = collectClasses(imports, packageName); shortener.addImports(imports, includedClasses); for (String s : shortener.getImports()) { out("import %s;", s); } if (G.generateFQN && imports.contains("#forced")) { for (String s : JBIterable.from(imports).filter(o -> !"#forced".equals(o))) { 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.shorten(aSuper)); } else if (javaType != ParserGenerator.Java.INTERFACE && i == 1) { sb.append(" implements ").append(shortener.shorten(aSuper)); } else { sb.append(", ").append(shortener.shorten(aSuper)); } } if (StringUtil.isNotEmpty(annos)) { out(shortener.shorten(annos)); } out("public %s %s%s {", Case.LOWER.apply(javaType.name()).replace('_', ' '), shortClassName, sb.toString()); newLine(); myShortener = shortener; } protected abstract @NotNull Set<String> collectClasses(Set<String> imports, String packageName); protected 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); } resetOffset(); } @NotNull protected String generatePackageName(String className) { var packageName = StringUtil.getPackageName(className); if (myGenerateForFleet && !className.startsWith(FLEET_NAMESPACE)) { if (packageName.isEmpty()) { return FLEET_NAMESPACE; } return FLEET_NAMESPACE_PREFIX + packageName; } return packageName; } 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; } protected void openOutput(String className) throws IOException { String classNameAdjusted = myPackagePrefix.isEmpty() ? className : StringUtil.trimStart(className, myPackagePrefix + "."); File file = new File(myOutputPath, classNameAdjusted.replace('.', File.separatorChar) + ".java"); myOut = openOutputInner(className, file); } protected PrintWriter openOutputInner(String className, File file) throws IOException { //noinspection ResultOfMethodCallIgnored file.getParentFile().mkdirs(); return new PrintWriter(new FileOutputStream(file), false, this.myFile.getVirtualFile().getCharset()); } protected 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("}") || substring.startsWith(")"))) { myOffset--; newStatement = true; } if (myOffset > 0) { myOut.print(StringUtil.repeat(" ", newStatement ? myOffset : myOffset + 1)); } myOut.println(substring); if (isComment) { newStatement = true; } else if (substring.endsWith("{")) { myOffset++; newStatement = true; } else if (substring.endsWith("(")) { myOffset++; newStatement = false; } else { newStatement = substring.endsWith(";") || substring.endsWith("}"); } } } public void newLine() { out(""); } public @NotNull String shorten(@NotNull String s) { return myShortener.shorten(s); } protected void resetOffset() { myOffset = 0; } }