From d6572ce903ee9b89b37343b3de8fbc697f3271da Mon Sep 17 00:00:00 2001 From: KrylovBoris Date: Sat, 21 Sep 2024 23:49:46 +0300 Subject: [PATCH] Fleet-compatible generation for Grammar-Kit (#359) * [fleet] Fixing test compilation * [fleet] Adjusting Parser generation for Fleet * [fleet]Passing generate for Fleet as an option + modifying parse() function * [fleet]Making tests for Fleet * [fleet]Moving Fleet-related generation logic outside of general generator * [fleet]Fix import generation for Fleet * [fleet]Adding generation action for Fleet * [fleet]Refactor ParserGenerator * [fleet]Separate tests for fleet generator * [fleet]Lexer generator action for Fleet * [fleet]Improving import directives in output files * [fleet]Polishing imports in generated files * [fleet]Polishing tests for fleet * [fleet]Adding import setting support for lexer generation * [fleet]forcing main to generate Fleet files * [fleet]fix accidental typo * [fleet]Moving generated files to proper folder * [fleet]fix lexer generation location * [fleet] adjust type holder generation to not generate factory methods * [fleet] Generate additional fleet-specific files * [fleet] Adjust testing data * [fleet] Polishing outputs * [fleet] Polishing for the review * [fleet] Clarification on Main class usage * [fleet] adjusting GenOptions + including adjustPackagesForFleet * [fleet] Renamed fleet-related actions * [fleet] Cleaning-up test * [fleet] Removed IFileType generation from the BNF grammar * [fleet] Created "Run Fleet-Compatible JFlex Generator" * [fleet] adding filetype generation to the main function parameters + adjusting tests for the new API * [fleet] idea API version update * [fleet] fix lexer imports not adjusting to fleet * Revert "[fleet] idea API version update" This reverts commit 129503fc16660c456c6d27793da665d01ccf0de4. * Revert "[fleet] Fixing test compilation" This reverts commit f511e14cef7abe483d973799e5e7d1496244e316. * [fleet] configuration files rollback + removal of unused resource * [fleet] removing unintentional whitespaces * [fleet] hiding fleet-related items for fleet-unrelated projects + changing string prompts * [fleet] reverting "hiding fleet-related items for fleet-unrelated projects" * [fleet] fixing BnfRunFleetJFlexAction adding "fleet." to "fleet." * [fleet] updating CHANGELOG.md * [fleet] Extract basic generator functionality to make a separate IFileType generator for fleet * [fleet] remove unused getAllPossibleAttributeValues * [fleet] remove unused GenOptions parameter from FleetFileTypeGenerator constructor * [fleet] implement FleetBnfFileImpl that adjusts class names for Fleet * [fleet] extract adjustPackagesForFleet into a separate attribute for easier access from outside of GenOptions * [fleet] fix tests and test data * [fleet] add TOKEN_TYPE_FACTORY default value override * [fleet] improve naming and streamline logic in FleetBnfFileWrapper * [fleet] restructure FleetConstants * [fleet] refactor ParserGenerator so that ParserGenerator extends GeneratorBase * [fleet] fix hasAttributeValue logic * [fleet] make ExpressionGeneratorHelper reference constant set, not BnfConstants * [fleet] fix parser generation action * [fleet] adjust test data * [fleet] fix Main after refactoring * [fleet] refactor contxt creation for generate lexer action * [fleet] move adjustPackages attribute inside generate attribute * [fleet] adjust test data for testExprParser * [fleet] adjust code style * [fleet] Move RuleInfo + NameShortener to ParserGenerator * [fleet] Move RuleInfo + NameShortener to ParserGenerator * [fleet] rename ParserConstantSet to IntelliJPlatformConstants * [fleet] cleanup GenerateAction * [fleet] move fleet-related classes to flat "fleet" package * [fleet] move fleet-related classes to flat "fleet" package * [fleet] adding spaces * [fleet] simplifying BnfRunJFlexAction * [fleet] remove json.bnf from test data * [fleet] reformat test + inline myFile * [fleet] remove adjustPackagesForFleet GenOption * [fleet] rename ExprParser test and add FleetExternalRules test * [fleet] replace FleetBnfFileWrapper as PsiFile for BNF for Fleet-related generation * [fleet] refactor FleetBnfFileWrapper creation * [fleet] rename file to minimize diff with master * [fleet] add explanation for forceCachedPsi(wrapped) call in FleetBnfFileWrapper.wrapBnfFile --------- Co-authored-by: Boris Krylov --- CHANGELOG.md | 3 + resources/META-INF/MANIFEST.MF | 8 +- resources/META-INF/plugin-java.xml | 4 + .../messages/GrammarKitBundle.properties | 3 + resources/templates/fleet.lexer.flex.template | 47 + src/org/intellij/grammar/LightPsi.java | 5 + src/org/intellij/grammar/Main.java | 95 +- .../actions/BnfGenerateLexerAction.java | 36 +- .../grammar/actions/BnfRunJFlexAction.java | 31 +- .../grammar/actions/GenerateAction.java | 11 +- .../fleet/BnfGenerateFleetLexerAction.java | 53 ++ .../grammar/fleet/BnfRunFleetJFlexAction.java | 126 +++ .../grammar/fleet/FleetBnfFileWrapper.java | 126 +++ .../grammar/fleet/FleetConstants.java | 24 + .../grammar/fleet/FleetFileTypeGenerator.java | 75 ++ .../grammar/fleet/GenerateFleetAction.java | 22 + .../generator/ExpressionGeneratorHelper.java | 5 +- .../grammar/generator/GenOptions.java | 8 +- .../grammar/generator/GeneratorBase.java | 230 +++++ .../generator/IntelliJPlatformConstants.java | 57 ++ .../grammar/generator/ParserGenerator.java | 277 ++---- .../grammar/psi/impl/BnfFileImpl.java | 6 +- .../fleet/FleetExprParser.PSI.expected.java | 42 + testData/fleet/FleetExprParser.bnf | 79 ++ testData/fleet/FleetExprParser.expected.java | 426 +++++++++ testData/fleet/FleetExternalRules.bnf | 103 ++ .../fleet/FleetExternalRules.expected.java | 889 ++++++++++++++++++ testData/fleet/FleetPsiGen.PSI.expected.java | 45 + testData/fleet/FleetPsiGen.bnf | 76 ++ testData/fleet/FleetPsiGen.expected.java | 779 +++++++++++++++ testData/fleet/IFIleTypeGeneration.bnf | 9 + .../IFileTypeGeneration.PSI.expected.java | 37 + .../fleet/IFileTypeGeneration.expected.java | 161 ++++ .../grammar/BnfGeneratorAbstractTest.java | 93 ++ .../intellij/grammar/BnfGeneratorTest.java | 76 +- tests/org/intellij/grammar/BnfTestSuite.java | 1 + .../grammar/FleetBnfGeneratorTest.java | 68 ++ 37 files changed, 3809 insertions(+), 327 deletions(-) create mode 100644 resources/templates/fleet.lexer.flex.template create mode 100644 src/org/intellij/grammar/fleet/BnfGenerateFleetLexerAction.java create mode 100644 src/org/intellij/grammar/fleet/BnfRunFleetJFlexAction.java create mode 100644 src/org/intellij/grammar/fleet/FleetBnfFileWrapper.java create mode 100644 src/org/intellij/grammar/fleet/FleetConstants.java create mode 100644 src/org/intellij/grammar/fleet/FleetFileTypeGenerator.java create mode 100644 src/org/intellij/grammar/fleet/GenerateFleetAction.java create mode 100644 src/org/intellij/grammar/generator/GeneratorBase.java create mode 100644 src/org/intellij/grammar/generator/IntelliJPlatformConstants.java create mode 100644 testData/fleet/FleetExprParser.PSI.expected.java create mode 100644 testData/fleet/FleetExprParser.bnf create mode 100644 testData/fleet/FleetExprParser.expected.java create mode 100644 testData/fleet/FleetExternalRules.bnf create mode 100644 testData/fleet/FleetExternalRules.expected.java create mode 100644 testData/fleet/FleetPsiGen.PSI.expected.java create mode 100644 testData/fleet/FleetPsiGen.bnf create mode 100644 testData/fleet/FleetPsiGen.expected.java create mode 100644 testData/fleet/IFIleTypeGeneration.bnf create mode 100644 testData/fleet/IFileTypeGeneration.PSI.expected.java create mode 100644 testData/fleet/IFileTypeGeneration.expected.java create mode 100644 tests/org/intellij/grammar/BnfGeneratorAbstractTest.java create mode 100644 tests/org/intellij/grammar/FleetBnfGeneratorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6057e970..9bca7b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## [Unreleased] * JFlex: update library from jflex-1.9.1 to jflex-1.9.2 #349 * Compatibility: IntelliJ IDEA 2024.2 +* Context menu: Fleet: Generate Parser Code +* Context menu: Fleet: Generate JFlex Lexer +* Context menu: Fleet: Run JFlex Generator ## [2022.3.2] * JFlex: complete classes in java references diff --git a/resources/META-INF/MANIFEST.MF b/resources/META-INF/MANIFEST.MF index f1213e39..83d6036d 100644 --- a/resources/META-INF/MANIFEST.MF +++ b/resources/META-INF/MANIFEST.MF @@ -1,8 +1,10 @@ Manifest-Version: 1.0 Class-Path: light-psi-all.jar - lib/3rd-party-rt.jar - lib/platform-api.jar - lib/platform-impl.jar lib/util.jar + lib/util-8.jar + lib/util_rt.jar + lib/app-client.jar + lib/lib-client.jar + lib/opentelemetry.jar Main-Class: org.intellij.grammar.Main diff --git a/resources/META-INF/plugin-java.xml b/resources/META-INF/plugin-java.xml index b5df3ef2..59593986 100644 --- a/resources/META-INF/plugin-java.xml +++ b/resources/META-INF/plugin-java.xml @@ -22,6 +22,10 @@ + + + + diff --git a/resources/messages/GrammarKitBundle.properties b/resources/messages/GrammarKitBundle.properties index 4617d8c1..d35596c3 100644 --- a/resources/messages/GrammarKitBundle.properties +++ b/resources/messages/GrammarKitBundle.properties @@ -14,6 +14,9 @@ action.grammar.Generate.JFlexLexer.text=Generate JFlex Lexer action.grammar.Generate.ParserUtil.text=Generate Parser Util action.grammar.Run.JFlex.text=Run JFlex Generator action.grammar.Generate.text=Generate Parser Code +action.grammar.fleet.Generate.text=Fleet: Generate Parser Code +action.grammar.fleet.Generate.JFlexLexer.text=Fleet: Generate JFlex Lexer +action.grammar.fleet.Run.JFlex.text=Fleet: Run JFlex Generator intention.category.bnf=Grammar-Kit BNF intention.flip.arguments.text=Flip arguments diff --git a/resources/templates/fleet.lexer.flex.template b/resources/templates/fleet.lexer.flex.template new file mode 100644 index 00000000..aeebf439 --- /dev/null +++ b/resources/templates/fleet.lexer.flex.template @@ -0,0 +1,47 @@ +package $packageName; + +import fleet.com.intellij.lexer.FlexLexer; +import fleet.com.intellij.psi.tree.IElementType; + +import static fleet.com.intellij.psi.TokenType.BAD_CHARACTER; +import static fleet.com.intellij.psi.TokenType.WHITE_SPACE; +import static $typesClass.*; + +%% + +%{ + public $lexerClass() { + this((java.io.Reader)null); + } +%} + +%public +%class $lexerClass +%implements FlexLexer +%function advance +%type IElementType +%unicode + +EOL=\R +WHITE_SPACE=\s+ + +#foreach( $token in $regexpTokens.keySet() ) +$token=$regexpTokens.get($token) +#end + +#macro(spaces $len) #set( $count = $maxTokenLength - $len ) $StringUtil.repeat(" ", $count) #end +%% + { + {WHITE_SPACE} #spaces(11) { return WHITE_SPACE; } + +#foreach( $token in $simpleTokens.keySet() ) + "$simpleTokens.get($token)" #spaces($simpleTokens.get($token).length()) { return ${tokenPrefix}$token; } +#end + +#foreach( $token in $regexpTokens.keySet() ) + {$token} #spaces($token.length()) { return ${tokenPrefix}$token; } +#end + +} + +[^] { return BAD_CHARACTER; } diff --git a/src/org/intellij/grammar/LightPsi.java b/src/org/intellij/grammar/LightPsi.java index 119d710a..6119d910 100644 --- a/src/org/intellij/grammar/LightPsi.java +++ b/src/org/intellij/grammar/LightPsi.java @@ -128,6 +128,11 @@ private static boolean shouldAddEntry(String path) { path.contains("platform-api.jar") || path.contains("platform-impl.jar") || path.contains("util.jar") || + path.contains("util-8.jar") || + path.contains("util_rt.jar") || + path.contains("app-client.jar") || + path.contains("lib-client.jar") || + path.contains("opentelemetry.jar") || path.contains("extensions.jar"); } diff --git a/src/org/intellij/grammar/Main.java b/src/org/intellij/grammar/Main.java index 85fdd3a4..b88be002 100644 --- a/src/org/intellij/grammar/Main.java +++ b/src/org/intellij/grammar/Main.java @@ -10,6 +10,8 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.impl.DebugUtil; import org.intellij.grammar.generator.ParserGenerator; +import org.intellij.grammar.fleet.FleetBnfFileWrapper; +import org.intellij.grammar.fleet.FleetFileTypeGenerator; import org.intellij.grammar.psi.BnfFile; import java.io.File; @@ -18,16 +20,16 @@ /** * Command-line interface to parser generator. * Required community jars on classpath: - * jdom.jar, trove4j.jar, extensions.jar, picocontainer.jar, junit.jar, idea.jar, openapi.jar, util.jar. + * app-client.jar, lib-client.jar, opentelemetry.jar, util.jar, util-8.jar, util_rt.jar * * @author gregsh - * * @noinspection UseOfSystemOutOrSystemErr */ public class Main { public static void main(String[] args) { if (args.length < 2) { - System.out.println("Usage: Main "); + System.out.println( + "Usage: Main [--fleet] [--generateFileTypeElement --className= --debugName= --languageClass=] [ ... [--fleet] [--generateFileTypeElement...]]"); return; } File output = new File(args[0]); @@ -43,6 +45,12 @@ public static void main(String[] args) { BnfParserDefinition parserDefinition = new BnfParserDefinition(); for (int i = 1; i < args.length; i++) { + boolean generateForFleet = false; + boolean generateFileTypeElement = false; + String className = ""; + String languageClass = ""; + String debugName = "FILE"; + String grammar = args[i]; int idx = grammar.lastIndexOf(File.separator); File grammarDir = new File(idx >= 0 ? grammar.substring(0, idx) : "."); @@ -53,13 +61,73 @@ public static void main(String[] args) { return; } + while (i + 1 < args.length && (args[i + 1].startsWith("--fleet") || args[i + 1].startsWith("--generateFileTypeElement"))) { + i++; + var arg = args[i]; + if (arg.equals("--fleet")) { + generateForFleet = true; + } + if (arg.startsWith("--generateFileTypeElement")) { + var hasClassName = false; + var hasLanguageClass = false; + while (i + 1 < args.length && + (args[i + 1].startsWith("--className") || + args[i + 1].startsWith("--debugName") || + args[i + 1].startsWith("--languageClass"))) { + i++; + var argInner = args[i]; + if (argInner.startsWith("--className")) { + String[] keyValuePair = argInner.split("="); + if (keyValuePair.length == 2) { + className = keyValuePair[1]; + hasClassName = true; + } + else { + System.out.println("Error parsing parameters: " + argInner); + return; + } + } + if (argInner.startsWith("--languageClass")) { + String[] keyValuePair = argInner.split("="); + if (keyValuePair.length == 2) { + languageClass = keyValuePair[1]; + hasLanguageClass = true; + } + else { + System.out.println("Error parsing parameters: " + argInner); + return; + } + } + if (argInner.startsWith("--debugName")) { + String[] keyValuePair = argInner.split("="); + if (keyValuePair.length == 2) { + debugName = keyValuePair[1]; + } + else { + System.out.println("Error parsing parameters: " + argInner); + return; + } + } + } + + if (!hasClassName) { + System.out.println("Error parsing parameters: --className missing"); + return; + } + if (!hasLanguageClass) { + System.out.println("Error parsing parameters: --languageClass missing"); + return; + } + generateFileTypeElement = true; + } + } File[] files = grammarDir.listFiles(); int count = 0; if (files != null) { for (File file : files) { if (file.isDirectory() || !grammarPattern.matcher(file.getName()).matches()) continue; - PsiFile bnfFile = LightPsi.parseFile(file, parserDefinition); - if (!(bnfFile instanceof BnfFile)) continue; + PsiFile psiFile = LightPsi.parseFile(file, parserDefinition); + if (!(psiFile instanceof BnfFile)) continue; // for light-psi-all building: if (args[0].contains("lightpsi")) { @@ -67,16 +135,25 @@ public static void main(String[] args) { Class.forName("org.jetbrains.annotations.Nullable"); Class.forName("org.intellij.lang.annotations.Pattern"); Class.forName("org.intellij.lang.annotations.RegExp"); - DebugUtil.psiToString(bnfFile, false); + DebugUtil.psiToString(psiFile, false); + } + count++; + + BnfFile bnfFile = (generateForFleet) ? FleetBnfFileWrapper.wrapBnfFile((BnfFile)psiFile) : (BnfFile)psiFile; + new ParserGenerator(bnfFile, grammarDir.getAbsolutePath(), output.getAbsolutePath(), "").generate(); + if (generateFileTypeElement) { + new FleetFileTypeGenerator((BnfFile)psiFile, + grammarDir.getAbsolutePath(), + output.getAbsolutePath(), + "", + className, debugName, languageClass).generate(); } - count ++; - new ParserGenerator((BnfFile) bnfFile, grammarDir.getAbsolutePath(), output.getAbsolutePath(), "").generate(); System.out.println(file.getName() + " parser generated to " + output.getCanonicalPath()); } } if (count == 0) { - System.out.println("No grammars matching '"+wildCard+"' found in: "+ grammarDir); + System.out.println("No grammars matching '" + wildCard + "' found in: " + grammarDir); } } } diff --git a/src/org/intellij/grammar/actions/BnfGenerateLexerAction.java b/src/org/intellij/grammar/actions/BnfGenerateLexerAction.java index 604a6d27..815fd013 100644 --- a/src/org/intellij/grammar/actions/BnfGenerateLexerAction.java +++ b/src/org/intellij/grammar/actions/BnfGenerateLexerAction.java @@ -61,6 +61,13 @@ * @author greg */ public class BnfGenerateLexerAction extends AnAction { + + private static final String LEXER_FLEX_TEMPLATE = "/templates/lexer.flex.template"; + + protected String getLexerFlexTemplate() { + return LEXER_FLEX_TEMPLATE; + } + @Override public @NotNull ActionUpdateThread getActionUpdateThread() { return ActionUpdateThread.BGT; @@ -172,22 +179,31 @@ public void visitElement(@NotNull PsiElement element) { } catch (Throwable ignore) {} ve.init(); - - VelocityContext context = new VelocityContext(); + + VelocityContext context = makeContext(bnfFile, packageName, simpleTokens, regexpTokens, maxLen); + + StringWriter out = new StringWriter(); + InputStream stream = getClass().getResourceAsStream(getLexerFlexTemplate()); + ve.evaluate(context, out, "lexer.flex.template", new InputStreamReader(stream)); + return StringUtil.convertLineSeparators(out.toString()); + } + + protected VelocityContext makeContext(BnfFile bnfFile, + @Nullable String packageName, + Map simpleTokens, + Map regexpTokens, + int[] maxLen) { + var context = new VelocityContext(); context.put("lexerClass", getLexerName(bnfFile)); - context.put("packageName", StringUtil.notNullize(packageName, StringUtil.getPackageName(getRootAttribute(bnfFile, KnownAttribute.PARSER_CLASS)))); + context.put("packageName", + StringUtil.notNullize(packageName, StringUtil.getPackageName(getRootAttribute(bnfFile, KnownAttribute.PARSER_CLASS)))); context.put("tokenPrefix", getRootAttribute(bnfFile, KnownAttribute.ELEMENT_TYPE_PREFIX)); context.put("typesClass", getRootAttribute(bnfFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS)); - context.put("tokenPrefix", getRootAttribute(bnfFile, KnownAttribute.ELEMENT_TYPE_PREFIX)); context.put("simpleTokens", simpleTokens); context.put("regexpTokens", regexpTokens); context.put("StringUtil", StringUtil.class); context.put("maxTokenLength", maxLen[0]); - - StringWriter out = new StringWriter(); - InputStream stream = getClass().getResourceAsStream("/templates/lexer.flex.template"); - ve.evaluate(context, out, "lexer.flex.template", new InputStreamReader(stream)); - return StringUtil.convertLineSeparators(out.toString()); + return context; } public static @NotNull String token2JFlex(@NotNull String tokenText) { @@ -243,7 +259,7 @@ static String getFlexFileName(BnfFile bnfFile) { return getLexerName(bnfFile) + ".flex"; } - private static String getLexerName(BnfFile bnfFile) { + protected static String getLexerName(BnfFile bnfFile) { return "_" + BnfGenerateParserUtilAction.getGrammarName(bnfFile) + "Lexer"; } diff --git a/src/org/intellij/grammar/actions/BnfRunJFlexAction.java b/src/org/intellij/grammar/actions/BnfRunJFlexAction.java index 735c0ec0..163cea52 100644 --- a/src/org/intellij/grammar/actions/BnfRunJFlexAction.java +++ b/src/org/intellij/grammar/actions/BnfRunJFlexAction.java @@ -58,7 +58,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -91,7 +91,7 @@ public void update(@NotNull AnActionEvent e) { e.getPresentation().setEnabledAndVisible(project != null && !files.isEmpty()); } - private static List getFiles(@NotNull AnActionEvent e) { + protected static List getFiles(@NotNull AnActionEvent e) { return JBIterable.of(e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY)).filter(file -> { FileType fileType = file.getFileType(); return fileType == JFlexFileType.INSTANCE || @@ -115,21 +115,20 @@ public void actionPerformed(@NotNull AnActionEvent e) { return; } String batchId = "jflex@" + System.nanoTime(); - new Runnable() { - final Iterator it = files.iterator(); - @Override - public void run() { - if (it.hasNext()) { - doGenerate(project, it.next(), flexFiles, batchId).doWhenProcessed(this); - } - } - }.run(); + doGenerate(project, files, flexFiles, batchId); + } + + protected void doGenerate(@NotNull Project project, + Collection flexFiles, + @NotNull Couple jflex, + @NotNull String batchId){ + flexFiles.forEach(file -> doGenerateInner(project, file, jflex, batchId)); } - public static ActionCallback doGenerate(@NotNull Project project, - @NotNull VirtualFile flexFile, - @NotNull Couple jflex, - @NotNull String batchId) { + protected static ActionCallback doGenerateInner(@NotNull Project project, + @NotNull VirtualFile flexFile, + @NotNull Couple jflex, + @NotNull String batchId) { FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); Document document = fileDocumentManager.getDocument(flexFile); if (document == null) return ActionCallback.REJECTED; @@ -252,7 +251,7 @@ private static void attachAndActivate(@NotNull Project project, } } - private static @Nullable Couple getOrDownload(@NotNull Project project) { + protected static @Nullable Couple getOrDownload(@NotNull Project project) { LibraryTable libraryTable = LibraryTablesRegistrar.getInstance().getLibraryTable(); for (Library library : libraryTable.getLibraries()) { Couple result = findInUrls(library.getUrls(OrderRootType.CLASSES)); diff --git a/src/org/intellij/grammar/actions/GenerateAction.java b/src/org/intellij/grammar/actions/GenerateAction.java index 577cc33a..72716f61 100755 --- a/src/org/intellij/grammar/actions/GenerateAction.java +++ b/src/org/intellij/grammar/actions/GenerateAction.java @@ -34,6 +34,7 @@ import org.intellij.grammar.generator.ParserGenerator; import org.intellij.grammar.psi.BnfFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -83,7 +84,7 @@ public void actionPerformed(@NotNull AnActionEvent e) { return files.filter(o -> manager.findFile(o) instanceof BnfFile); } - public static void doGenerate(@NotNull Project project, @NotNull List bnfFiles) { + public void doGenerate(@NotNull Project project, @NotNull List bnfFiles) { Map rootMap = new LinkedHashMap<>(); Map packageMap = new LinkedHashMap<>(); PsiManager psiManager = PsiManager.getInstance(project); @@ -91,7 +92,7 @@ public static void doGenerate(@NotNull Project project, @NotNull List { for (VirtualFile file : bnfFiles) { if (!file.isValid()) continue; - PsiFile bnfFile = psiManager.findFile(file); + PsiFile bnfFile = getBnfFile(file, psiManager); if (!(bnfFile instanceof BnfFile)) continue; String parserClass = getRootAttribute(bnfFile, KnownAttribute.PARSER_CLASS); VirtualFile target = @@ -150,7 +151,7 @@ private void runInner(ProgressIndicator indicator) { try { DumbService.getInstance(project).runReadActionInSmartMode(() -> { if (!file.isValid()) return; - PsiFile bnfFile = psiManager.findFile(file); + PsiFile bnfFile = getBnfFile(file, psiManager); if (!(bnfFile instanceof BnfFile)) return; ParserGenerator generator = new ParserGenerator((BnfFile)bnfFile, sourcePath, genDir.getPath(), packagePrefix) { @Override @@ -194,4 +195,8 @@ protected PrintWriter openOutputInner(String className, File file) throws IOExce } }); } + + protected @Nullable PsiFile getBnfFile(VirtualFile file, PsiManager psiManager) { + return psiManager.findFile(file); + } } diff --git a/src/org/intellij/grammar/fleet/BnfGenerateFleetLexerAction.java b/src/org/intellij/grammar/fleet/BnfGenerateFleetLexerAction.java new file mode 100644 index 00000000..79c454d9 --- /dev/null +++ b/src/org/intellij/grammar/fleet/BnfGenerateFleetLexerAction.java @@ -0,0 +1,53 @@ +/* + * 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.fleet; + +import com.intellij.openapi.util.text.StringUtil; +import org.apache.velocity.VelocityContext; +import org.intellij.grammar.KnownAttribute; +import org.intellij.grammar.actions.BnfGenerateLexerAction; +import org.intellij.grammar.psi.BnfFile; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +import static org.intellij.grammar.generator.ParserGeneratorUtil.getRootAttribute; +import static org.intellij.grammar.fleet.FleetConstants.*; + +public class BnfGenerateFleetLexerAction extends BnfGenerateLexerAction { + + + private static final String FLEET_LEXER_FLEX_TEMPLATE = "/templates/fleet.lexer.flex.template"; + + @Override + protected String getLexerFlexTemplate() { + return FLEET_LEXER_FLEX_TEMPLATE; + } + + + @Override + protected VelocityContext makeContext(BnfFile bnfFile, + @Nullable String packageName, + Map simpleTokens, + Map regexpTokens, + int[] maxLen) { + var context = new VelocityContext(); + context.put("lexerClass", getLexerName(bnfFile)); + var original = StringUtil.notNullize(packageName, StringUtil.getPackageName(getRootAttribute(bnfFile, KnownAttribute.PARSER_CLASS))); + if (!original.isEmpty()) { + context.put("packageName", FLEET_NAMESPACE_PREFIX + original); + } + else { + context.put("packageName", FLEET_NAMESPACE); + } + context.put("tokenPrefix", getRootAttribute(bnfFile, KnownAttribute.ELEMENT_TYPE_PREFIX)); + context.put("typesClass", FLEET_NAMESPACE_PREFIX + getRootAttribute(bnfFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS)); + context.put("simpleTokens", simpleTokens); + context.put("regexpTokens", regexpTokens); + context.put("StringUtil", StringUtil.class); + context.put("maxTokenLength", maxLen[0]); + return context; + } +} diff --git a/src/org/intellij/grammar/fleet/BnfRunFleetJFlexAction.java b/src/org/intellij/grammar/fleet/BnfRunFleetJFlexAction.java new file mode 100644 index 00000000..1119e9e2 --- /dev/null +++ b/src/org/intellij/grammar/fleet/BnfRunFleetJFlexAction.java @@ -0,0 +1,126 @@ +/* + * 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.fleet; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Couple; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.intellij.grammar.actions.BnfRunJFlexAction; +import org.intellij.grammar.java.JavaHelper; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.nio.file.Files; +import java.util.Collection; + +public class BnfRunFleetJFlexAction extends BnfRunJFlexAction { + private static final String TEMP_FLEX_DIRECTORY = "temp-grammar-kit"; + private static final String PACKAGE_PREFIX = "package "; + private static final String IMPORT_PREFIX = "import "; + private static final String IMPORT_STATIC_PREFIX = "import static "; + + private static final String IELEMENTTYPE_CLASS = "com.intellij.psi.tree.IElementType"; + private static final String FLEX_LEXER_CLASS = "com.intellij.lexer.FlexLexer"; + private static final String WHITESPACE_TOKEN = "com.intellij.psi.TokenType.WHITE_SPACE"; + private static final String BAD_CHARACTER_TOKEN = "com.intellij.psi.TokenType.BAD_CHARACTER"; + + private JavaHelper javaHelper; + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + var project = e.getProject(); + if (project == null) return; + + javaHelper = project.getService(JavaHelper.class); + javaHelper = javaHelper == null ? new JavaHelper.AsmHelper() : javaHelper; + + super.actionPerformed(e); + } + + @Override + public void doGenerate(@NotNull Project project, + @NotNull Collection flexFiles, + @NotNull Couple jflex, + @NotNull String batchId) { + flexFiles.stream().map(f -> { + try { + if (f == null) return null; + var tempFileDirectory = f.getParent().getPath() + VfsUtil.VFS_SEPARATOR + TEMP_FLEX_DIRECTORY; + var newFile = new File(tempFileDirectory, f.getName()); + newFile.getParentFile().mkdirs(); + var printer = new PrintWriter(new FileOutputStream(newFile), false, f.getCharset()); + Files.readAllLines(f.toNioPath()).forEach(line -> { + if (line.startsWith(PACKAGE_PREFIX) || line.startsWith(IMPORT_PREFIX) || line.startsWith(IMPORT_STATIC_PREFIX)) { + printer.println(adjustLine(line, javaHelper)); + } + else { + printer.println(line); + } + }); + printer.close(); + return VfsUtil.findFileByIoFile(newFile, true); + } + catch (Exception ignored) { + return null; + } + }).forEach(flexFile -> { + if (flexFile == null) return; + var result = doGenerateInner(project, flexFile, jflex, batchId); + result.doWhenProcessed(() -> { + try { + FileUtil.delete(flexFile.getParent().toNioPath()); + VfsUtil.markDirtyAndRefresh(true, false, true, flexFile.getParent()); + } + catch (IOException e) { + throw new RuntimeException(e); + } + }); + }); + } + + private static String adjustLine(String line, JavaHelper javaHelper) { + var tokens = line.split("[ ;]"); + var name = tokens[tokens.length - 1]; + + if ((line.startsWith(PACKAGE_PREFIX) && (!name.startsWith(FleetConstants.FLEET_NAMESPACE_PREFIX))) || + nameNeedsAdjusting(name, javaHelper)) { + name = FleetConstants.FLEET_NAMESPACE_PREFIX + name; + StringBuilder lineBuilder = new StringBuilder(); + for (int i = 0; i < tokens.length - 1; i++) { + lineBuilder.append(tokens[i]).append(" "); + } + return lineBuilder + name + ';'; + } + return line; + } + + private static Boolean nameNeedsAdjusting(String className, JavaHelper javaHelper) { + if (className.startsWith(FleetConstants.FLEET_NAMESPACE_PREFIX)) { + return false; + } + + if (className.equals(IELEMENTTYPE_CLASS) || + className.equals(FLEX_LEXER_CLASS) || + className.equals(WHITESPACE_TOKEN) || + className.equals(BAD_CHARACTER_TOKEN)) { + return true; + } + + var name = className; + if (className.endsWith(".*")) { + name = className.substring(0, className.length() - 2); + } + + var adjustedName = FleetConstants.FLEET_NAMESPACE_PREFIX + name; + if (javaHelper.findClass(adjustedName) != null || javaHelper.findPackage(adjustedName) != null) { + return true; + } + + return javaHelper.findClass(name) == null && javaHelper.findPackage(name) == null; + } +} \ No newline at end of file diff --git a/src/org/intellij/grammar/fleet/FleetBnfFileWrapper.java b/src/org/intellij/grammar/fleet/FleetBnfFileWrapper.java new file mode 100644 index 00000000..e61d3722 --- /dev/null +++ b/src/org/intellij/grammar/fleet/FleetBnfFileWrapper.java @@ -0,0 +1,126 @@ +/* + * 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.fleet; + +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.SingleRootFileViewProvider; +import org.intellij.grammar.KnownAttribute; +import org.intellij.grammar.psi.BnfAttr; +import org.intellij.grammar.psi.BnfAttrs; +import org.intellij.grammar.psi.BnfFile; +import org.intellij.grammar.psi.BnfRule; +import org.intellij.grammar.psi.impl.BnfFileImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +import static org.intellij.grammar.KnownAttribute.*; +import static org.intellij.grammar.fleet.FleetConstants.*; + +//Wraps BnfFile to produce fleet-related attribute values. +//Implemented as BnfFileImpl extension to avoid implementation of all methods in BnfFile interface +public class FleetBnfFileWrapper extends BnfFileImpl implements BnfFile { + + private final Map myFleetAttributeValuesSubstitution = Map.of( + PARSER_UTIL_CLASS.getName(), GPUB_CLASS, + ELEMENT_TYPE_CLASS.getName(), IELEMENTTYPE_CLASS, + TOKEN_TYPE_CLASS.getName(), IELEMENTTYPE_CLASS + ); + + private final Map myDefaultGeneratedNames = Map.of( + PARSER_CLASS.getName(), PARSER_CLASS_DEFAULT, + ELEMENT_TYPE_HOLDER_CLASS.getName(), ELEMENT_TYPE_HOLDER_DEFAULT + ); + + private final Set mySuppressedFactories = new HashSet<>(Arrays.asList(ELEMENT_TYPE_FACTORY.getName(), + TOKEN_TYPE_FACTORY.getName())); + + private FleetBnfFileWrapper(FileViewProvider viewProvider) { + super(viewProvider); + } + + public static FleetBnfFileWrapper wrapBnfFile(@NotNull BnfFile bnfFile) { + var viewProvider = bnfFile.getViewProvider(); + var wrapped = new FleetBnfFileWrapper(viewProvider); + //ViewProvider we use to construct FleetBnfFileWrapper cache BnfFile and returns it for getPsiInner(BnfLanguage). + //This may cause problems with, for example, producing LocalSearchScope, because it searches in cached psiFile, + //not the psiFile that was passed to it. + //To mitigate problems caused by this caching, we call forceCachedPsi(wrapped) to cache FleetBnfFileWrapper. + //It works, but legality of this is not clear. + if (viewProvider instanceof SingleRootFileViewProvider){ + ((SingleRootFileViewProvider)viewProvider).forceCachedPsi(wrapped); + } + return wrapped; + } + + @Override + public String toString() { + return "FleetBnfFile:" + getName(); + } + + @Override + public @NotNull List getRules() { + return super.getRules(); + } + + @Override + public @Nullable BnfRule getRule(@Nullable String ruleName) { + return super.getRule(ruleName); + } + + @Override + public @NotNull List getAttributes() { + return super.getAttributes(); + } + + @Override + public @Nullable BnfAttr findAttribute(@Nullable BnfRule rule, @NotNull KnownAttribute knownAttribute, @Nullable String match) { + return super.findAttribute(rule, knownAttribute, match); + } + + @Override + public T findAttributeValue(@Nullable BnfRule rule, @NotNull KnownAttribute knownAttribute, @Nullable String match) { + //Bypass adjustment logic for the GENERATE attribute + if (knownAttribute.getName().equals(GENERATE.getName())) { + return super.findAttributeValue(rule, knownAttribute, match); + } + + if (myFleetAttributeValuesSubstitution.containsKey(knownAttribute.getName())) { + if (hasAttributeValue(rule, knownAttribute, match)) { + return adjustedValue(rule, knownAttribute, match); + } + else { + return (T)myFleetAttributeValuesSubstitution.get(knownAttribute.getName()); + } + } + + ////If a generated element name has been requested, return value adjusted accordingly + if (myDefaultGeneratedNames.containsKey(knownAttribute.getName())) { + return adjustedValue(rule, knownAttribute, match); + } + + //If a factory attribute is requested, return null to force generation of non-factory methods + if (mySuppressedFactories.contains(knownAttribute.getName())) { + return null; + } + + return super.findAttributeValue(rule, knownAttribute, match); + } + + private T adjustedValue(@Nullable BnfRule rule, @NotNull KnownAttribute knownAttribute, @Nullable String match) { + var origin = super.findAttributeValue(rule, knownAttribute, match); + if (origin instanceof String && !((String)origin).startsWith(FLEET_NAMESPACE_PREFIX)) { + return (T)(FLEET_NAMESPACE_PREFIX + origin); + } + return origin; + } + + @Override + public @NotNull FileType getFileType() { + return super.getFileType(); + } +} diff --git a/src/org/intellij/grammar/fleet/FleetConstants.java b/src/org/intellij/grammar/fleet/FleetConstants.java new file mode 100644 index 00000000..8b88baf5 --- /dev/null +++ b/src/org/intellij/grammar/fleet/FleetConstants.java @@ -0,0 +1,24 @@ +/* + * 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.fleet; + +public interface FleetConstants { + String GPUB_CLASS = "fleet.com.intellij.lang.parser.GeneratedParserUtilBase"; + String PSI_BUILDER_CLASS = "fleet.com.intellij.lang.PsiBuilder"; + String PSI_PARSER_CLASS = "fleet.com.intellij.lang.PsiParser"; + + String FLEET_FILE_ELEMENT_TYPE_CLASS = "fleet.com.intellij.psi.tree.IFileElementType"; + String LIGHT_PSI_PARSER_CLASS = "fleet.com.intellij.lang.LightPsiParser"; + String TOKEN_SET_CLASS = "fleet.com.intellij.psi.tree.TokenSet"; + String IELEMENTTYPE_CLASS = "fleet.com.intellij.psi.tree.IElementType"; + String PSI_ELEMENT_CLASS = "fleet.com.intellij.psi.PsiElement"; + String AST_NODE_CLASS = "fleet.com.intellij.lang.ASTNode"; + + String PARSER_CLASS_DEFAULT = "fleet.generated.GeneratedParser"; + String ELEMENT_TYPE_HOLDER_DEFAULT = "fleet.generated.GeneratedTypes"; + + String FLEET_NAMESPACE_PREFIX = "fleet."; + String FLEET_NAMESPACE = "fleet"; +} diff --git a/src/org/intellij/grammar/fleet/FleetFileTypeGenerator.java b/src/org/intellij/grammar/fleet/FleetFileTypeGenerator.java new file mode 100644 index 00000000..3ee50554 --- /dev/null +++ b/src/org/intellij/grammar/fleet/FleetFileTypeGenerator.java @@ -0,0 +1,75 @@ +/* + * 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.fleet; + +import org.intellij.grammar.generator.BnfConstants; +import org.intellij.grammar.generator.GeneratorBase; +import org.intellij.grammar.psi.BnfFile; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.*; + +public class FleetFileTypeGenerator extends GeneratorBase { + + private final String myFileTypeClassName; + private final String myFileTypeDebugName; + private final String myLanguageClass; + + public FleetFileTypeGenerator(@NotNull BnfFile psiFile, + @NotNull String sourcePath, + @NotNull String outputPath, + @NotNull String packagePrefix, + String classFileTypeName, + String debugFileTypeName, + String languageClass) { + super(psiFile, sourcePath, outputPath, packagePrefix); + myFileTypeClassName = (!classFileTypeName.startsWith(FleetConstants.FLEET_NAMESPACE_PREFIX) + ? FleetConstants.FLEET_NAMESPACE_PREFIX + : "") + classFileTypeName; + myLanguageClass = (!languageClass.startsWith(FleetConstants.FLEET_NAMESPACE_PREFIX) ? FleetConstants.FLEET_NAMESPACE_PREFIX : "") + + languageClass; + myFileTypeDebugName = debugFileTypeName; + } + + @Override + public void generate() throws IOException { + openOutput(myFileTypeClassName); + try { + generateFileTypeClass(); + } + finally { + closeOutput(); + } + } + + @Override + protected @NotNull Set collectClasses(Set imports, String packageName) { + return Set.of(); + } + + private void generateFileTypeClass() { + var imports = new HashSet(); + imports.add(myLanguageClass); + imports.add(FleetConstants.FLEET_FILE_ELEMENT_TYPE_CLASS); + imports.add(FleetConstants.PSI_BUILDER_CLASS); + imports.add(myGrammarRootParser); + imports.add(BnfConstants.NOTNULL_ANNO); + + generateClassHeader(myFileTypeClassName, imports, "", Java.CLASS, FleetConstants.FLEET_FILE_ELEMENT_TYPE_CLASS); + + out("public static final %s INSTANCE = new %s();", shorten(myFileTypeClassName), shorten(myFileTypeClassName)); + newLine(); + out("public %s() {", shorten(myFileTypeClassName)); + out("super(\"%s\", %s.INSTANCE);", shorten(myFileTypeDebugName), shorten(myLanguageClass)); + out("}"); + newLine(); + out(shorten(BnfConstants.OVERRIDE_ANNO)); + out("public void parse(%s %s builder) {", shorten(BnfConstants.NOTNULL_ANNO), shorten(FleetConstants.PSI_BUILDER_CLASS)); + out("new %s().parseLight(this, builder);", shorten(myGrammarRootParser)); + out("}"); + out("}"); + } +} diff --git a/src/org/intellij/grammar/fleet/GenerateFleetAction.java b/src/org/intellij/grammar/fleet/GenerateFleetAction.java new file mode 100644 index 00000000..e732cb04 --- /dev/null +++ b/src/org/intellij/grammar/fleet/GenerateFleetAction.java @@ -0,0 +1,22 @@ +/* + * 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.fleet; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import org.intellij.grammar.actions.GenerateAction; +import org.intellij.grammar.psi.BnfFile; +import org.jetbrains.annotations.Nullable; + +public class GenerateFleetAction extends GenerateAction { + + @Override + protected @Nullable PsiFile getBnfFile(VirtualFile file, PsiManager psiManager) { + var psiFile = super.getBnfFile(file, psiManager); + if (psiFile == null) return null; + return FleetBnfFileWrapper.wrapBnfFile((BnfFile)psiFile); + } +} diff --git a/src/org/intellij/grammar/generator/ExpressionGeneratorHelper.java b/src/org/intellij/grammar/generator/ExpressionGeneratorHelper.java index bfc7fbce..ed7b661e 100644 --- a/src/org/intellij/grammar/generator/ExpressionGeneratorHelper.java +++ b/src/org/intellij/grammar/generator/ExpressionGeneratorHelper.java @@ -17,7 +17,6 @@ import java.util.*; import static java.lang.String.format; -import static org.intellij.grammar.generator.BnfConstants.PSI_BUILDER_CLASS; import static org.intellij.grammar.generator.ExpressionHelper.OperatorInfo; import static org.intellij.grammar.generator.ExpressionHelper.OperatorType; import static org.intellij.grammar.generator.ParserGeneratorUtil.*; @@ -53,8 +52,8 @@ public static void generateExpressionRoot(ExpressionHelper.ExpressionInfo info, String methodName = getFuncName(info.rootRule); String kernelMethodName = getNextName(methodName, 0); String frameName = quote(getRuleDisplayName(info.rootRule, true)); - String shortPB = g.shorten(PSI_BUILDER_CLASS); - String shortMarker = !g.G.generateFQN ? "Marker" : PSI_BUILDER_CLASS + ".Marker"; + String shortPB = g.shorten(g.C.PsiBuilderClass); + String shortMarker = !g.G.generateFQN ? "Marker" : g.C.PsiBuilderClass + ".Marker"; g.out("public static boolean %s(%s %s, int %s, int %s) {", methodName, shortPB, g.N.builder, g.N.level, g.N.priority); g.out("if (!recursion_guard_(%s, %s, \"%s\")) return false;", g.N.builder, g.N.level, methodName); diff --git a/src/org/intellij/grammar/generator/GenOptions.java b/src/org/intellij/grammar/generator/GenOptions.java index f892ba79..18a51259 100644 --- a/src/org/intellij/grammar/generator/GenOptions.java +++ b/src/org/intellij/grammar/generator/GenOptions.java @@ -6,6 +6,7 @@ import com.intellij.openapi.util.text.StringUtil; import org.intellij.grammar.KnownAttribute; +import org.intellij.grammar.fleet.FleetBnfFileWrapper; import org.intellij.grammar.psi.BnfFile; import java.util.Map; @@ -37,11 +38,12 @@ public class GenOptions { public final int javaVersion; public GenOptions(BnfFile myFile) { + var generateForFleet = myFile instanceof FleetBnfFileWrapper; Map genOptions = getRootAttribute(myFile, KnownAttribute.GENERATE).asMap(); names = Names.forName(genOptions.get("names")); - generatePsi = getGenerateOption(myFile, KnownAttribute.GENERATE_PSI, genOptions, "psi"); - generatePsiFactory = !"no".equals(genOptions.get("psi-factory")); - generatePsiClassesMap = "yes".equals(genOptions.get("psi-classes-map")); + generatePsi = getGenerateOption(myFile, KnownAttribute.GENERATE_PSI, genOptions, "psi") && !generateForFleet; + generatePsiFactory = !"no".equals(genOptions.get("psi-factory")) && !generateForFleet; + generatePsiClassesMap = "yes".equals(genOptions.get("psi-classes-map")) && !generateForFleet; generateTokenTypes = getGenerateOption(myFile, KnownAttribute.GENERATE_TOKENS, genOptions, "tokens"); generateTokenSets = generateTokenTypes && "yes".equals(genOptions.get("token-sets")); generateElementTypes = !"no".equals(genOptions.get("elements")); diff --git a/src/org/intellij/grammar/generator/GeneratorBase.java b/src/org/intellij/grammar/generator/GeneratorBase.java new file mode 100644 index 00000000..425d097d --- /dev/null +++ b/src/org/intellij/grammar/generator/GeneratorBase.java @@ -0,0 +1,230 @@ +/* + * 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 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 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 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 collectClasses(Set 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; + } +} diff --git a/src/org/intellij/grammar/generator/IntelliJPlatformConstants.java b/src/org/intellij/grammar/generator/IntelliJPlatformConstants.java new file mode 100644 index 00000000..b6c31fd1 --- /dev/null +++ b/src/org/intellij/grammar/generator/IntelliJPlatformConstants.java @@ -0,0 +1,57 @@ +/* + * 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 org.intellij.grammar.fleet.FleetBnfFileWrapper; +import org.intellij.grammar.fleet.FleetConstants; +import org.intellij.grammar.psi.BnfFile; + +public class IntelliJPlatformConstants { + public final String PsiBuilderClass; + public final String IElementTypeClass; + public final String PsiElementClass; + public final String AstNodeClass; + public final String PsiParserClass; + public final String LightPsiParserClass; + public final String TokenSetClass; + + private IntelliJPlatformConstants(String builder, + String iElementTypeClass, + String psiElementClass, + String astNodeClass, + String psiParserClass, + String lightPsiParserClass, + String tokenSetClass) { + PsiBuilderClass = builder; + IElementTypeClass = iElementTypeClass; + PsiElementClass = psiElementClass; + AstNodeClass = astNodeClass; + PsiParserClass = psiParserClass; + LightPsiParserClass = lightPsiParserClass; + TokenSetClass = tokenSetClass; + } + + public static final IntelliJPlatformConstants IdeaConstantSet = + new IntelliJPlatformConstants(BnfConstants.PSI_BUILDER_CLASS, + BnfConstants.IELEMENTTYPE_CLASS, + BnfConstants.PSI_ELEMENT_CLASS, + BnfConstants.AST_NODE_CLASS, + BnfConstants.PSI_PARSER_CLASS, + BnfConstants.LIGHT_PSI_PARSER_CLASS, + BnfConstants.TOKEN_SET_CLASS); + + public static final IntelliJPlatformConstants FleetConstantSet = + new IntelliJPlatformConstants(FleetConstants.PSI_BUILDER_CLASS, + FleetConstants.IELEMENTTYPE_CLASS, + FleetConstants.PSI_ELEMENT_CLASS, + FleetConstants.AST_NODE_CLASS, + FleetConstants.PSI_PARSER_CLASS, + FleetConstants.LIGHT_PSI_PARSER_CLASS, + FleetConstants.TOKEN_SET_CLASS); + + public static IntelliJPlatformConstants getConstantSetForBnf(BnfFile file) { + return (file instanceof FleetBnfFileWrapper) ? FleetConstantSet : IdeaConstantSet; + } +} diff --git a/src/org/intellij/grammar/generator/ParserGenerator.java b/src/org/intellij/grammar/generator/ParserGenerator.java index 6382b6b4..6fa95d87 100755 --- a/src/org/intellij/grammar/generator/ParserGenerator.java +++ b/src/org/intellij/grammar/generator/ParserGenerator.java @@ -10,7 +10,6 @@ import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Trinity; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.CommonClassNames; import com.intellij.psi.NavigatablePsiElement; @@ -31,10 +30,7 @@ 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.HashMap; import java.util.HashSet; import java.util.*; @@ -58,7 +54,7 @@ * @author gregory * Date 16.07.11 10:41 */ -public class ParserGenerator { +public class ParserGenerator extends GeneratorBase { public static final Logger LOG = Logger.getInstance(ParserGenerator.class); static class RuleInfo { @@ -120,26 +116,12 @@ RuleInfo ruleInfo(BnfRule rule) { private final Set myTokensUsedInGrammar = new LinkedHashSet<>(); private final boolean myNoStubs; - private final BnfFile myFile; - private final String mySourcePath; - private final String myOutputPath; - private final String myPackagePrefix; - private final String myGrammarRoot; - private final String myGrammarRootParser; - private final String myParserUtilClass; - private final String myPsiImplUtilClass; + protected final String myParserUtilClass; + protected final String myPsiImplUtilClass; private final String myPsiTreeUtilClass; - private final NameFormat myIntfClassFormat; - private final NameFormat myImplClassFormat; - - private final String myVisitorClassName; - private final String myTypeHolderClass; - - - private int myOffset; - private PrintWriter myOut; - private NameShortener myShortener; + protected final String myVisitorClassName; + protected final String myTypeHolderClass; private final RuleGraphHelper myGraphHelper; private final ExpressionHelper myExpressionHelper; @@ -147,23 +129,17 @@ RuleInfo ruleInfo(BnfRule rule) { private final BnfFirstNextAnalyzer myFirstNextAnalyzer; private final JavaHelper myJavaHelper; - final Names N; - final GenOptions G; + protected final Names N; + protected final IntelliJPlatformConstants C; public ParserGenerator(@NotNull BnfFile psiFile, @NotNull String sourcePath, @NotNull String outputPath, @NotNull String packagePrefix) { - myFile = psiFile; - mySourcePath = sourcePath; - myOutputPath = outputPath; - myPackagePrefix = packagePrefix; + super(psiFile, sourcePath, outputPath, packagePrefix); - G = new GenOptions(myFile); N = G.names; - myIntfClassFormat = getPsiClassFormat(myFile); - myImplClassFormat = 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); @@ -175,6 +151,8 @@ public ParserGenerator(@NotNull BnfFile psiFile, tmpVisitorClass : getRootAttribute(myFile, KnownAttribute.PSI_PACKAGE) + "." + tmpVisitorClass; myTypeHolderClass = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS); + C = IntelliJPlatformConstants.getConstantSetForBnf(myFile); + mySimpleTokens = new LinkedHashMap<>(getTokenTextToNameMap(myFile)); myGraphHelper = getCached(myFile); myExpressionHelper = new ExpressionHelper(myFile, myGraphHelper, this::addWarning); @@ -183,8 +161,6 @@ public ParserGenerator(@NotNull BnfFile psiFile, myJavaHelper = JavaHelper.getJavaHelper(myFile); List rules = psiFile.getRules(); - BnfRule rootRule = rules.isEmpty() ? null : rules.get(0); - myGrammarRoot = rootRule == null ? null : rootRule.getName(); for (BnfRule r : rules) { String ruleName = r.getName(); boolean noPsi = !hasPsiClass(r); @@ -196,7 +172,6 @@ public ParserGenerator(@NotNull BnfFile psiFile, noPsi ? null : getRulePsiClassName(r, myIntfClassFormat), noPsi ? null : getRulePsiClassName(r, myImplClassFormat), noPsi ? null : getAttribute(r, KnownAttribute.MIXIN), noPsi ? null : getAttribute(r, KnownAttribute.STUB_CLASS))); } - myGrammarRootParser = rootRule == null ? null : ruleInfo(rootRule).parserClass; myNoStubs = JBIterable.from(myRuleInfos.values()).find(o -> o.stub != null) == null; calcFakeRulesWithType(); @@ -298,71 +273,7 @@ public void addWarning(String text) { } } - private 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, myFile.getVirtualFile().getCharset()); - } - - 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("}") || 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); - } - + @Override public void generate() throws IOException { { generateParser(); @@ -516,7 +427,6 @@ private void generateVisitor(String psiClass, Map sortedRules) out("}"); } - public void generateParser() throws IOException { Map> classified = ContainerUtil.classify(myRuleInfos.values().iterator(), o -> o.parserClass); for (String className : ContainerUtil.sorted(classified.keySet())) { @@ -535,8 +445,8 @@ public void generateParser(String parserClass, Collection ownRuleNames) boolean rootParser = parserClass.equals(myGrammarRootParser); Set imports = new LinkedHashSet<>(); if (!G.generateFQN) { - imports.add(PSI_BUILDER_CLASS); - imports.add(PSI_BUILDER_CLASS + ".Marker"); + imports.add(C.PsiBuilderClass); + imports.add(C.PsiBuilderClass + ".Marker"); } else { imports.add("#forced"); @@ -552,19 +462,19 @@ public void generateParser(String parserClass, Collection ownRuleNames) imports.add(staticStarImport(myGrammarRootParser)); } else if (!G.generateFQN) { - imports.addAll(Arrays.asList(IELEMENTTYPE_CLASS, - AST_NODE_CLASS, - TOKEN_SET_CLASS, - PSI_PARSER_CLASS, - LIGHT_PSI_PARSER_CLASS)); + imports.addAll(Arrays.asList(C.IElementTypeClass, + C.AstNodeClass, + C.TokenSetClass, + C.PsiParserClass, + C.LightPsiParserClass)); } imports.addAll(parserImports); generateClassHeader(parserClass, imports, SUPPRESS_WARNINGS_ANNO + "({\"SimplifiableIfStatement\", \"UnusedAssignment\"})", Java.CLASS, "", - rootParser ? PSI_PARSER_CLASS : "", - rootParser ? LIGHT_PSI_PARSER_CLASS : ""); + rootParser ? C.PsiParserClass : "", + rootParser ? C.LightPsiParserClass : ""); if (rootParser) { generateRootParserContent(); @@ -610,7 +520,7 @@ private void generateParserLambdas(@NotNull String parserClass) { return G.javaVersion > 6 ? format("(%s, %s) -> %s", N.builder, N.level, body) : format("new Parser() {\npublic boolean parse(%s %s, int %s) {\nreturn %s;\n}\n}", - shorten(PSI_BUILDER_CLASS), N.builder, N.level, body); + shorten(C.PsiBuilderClass), N.builder, N.level, body); } private void generateMetaMethodFields() { @@ -633,11 +543,11 @@ private void generateRootParserContent() { List> extendsSet = buildExtendsSet(myGraphHelper.getRuleExtendsMap()); boolean generateExtendsSets = !extendsSet.isEmpty(); - String shortET = shorten(IELEMENTTYPE_CLASS); - String shortAN = shorten(AST_NODE_CLASS); - String shortPB = shorten(PSI_BUILDER_CLASS); - String shortTS = shorten(TOKEN_SET_CLASS); - String shortMarker = !G.generateFQN ? "Marker" : PSI_BUILDER_CLASS + ".Marker"; + String shortET = shorten(C.IElementTypeClass); + String shortAN = shorten(C.AstNodeClass); + String shortPB = shorten(C.PsiBuilderClass); + String shortTS = shorten(C.TokenSetClass); + String shortMarker = !G.generateFQN ? "Marker" : C.PsiBuilderClass + ".Marker"; out("public %s parse(%s %s, %s %s) {", shortAN, shortET, N.root, shortPB, N.builder); out("parseLight(%s, %s);", N.root, N.builder); out("return %s.getTreeBuilt();", N.builder); @@ -724,17 +634,8 @@ private void generateRootParserContent() { return result; } - private enum Java { CLASS, INTERFACE, ABSTRACT_CLASS } - private void generateClassHeader(String className, - Set imports, - String annos, - Java javaType, - String... supers) { - generateFileHeader(className); - String packageName = StringUtil.getPackageName(className); - String shortClassName = StringUtil.getShortName(className); - out("package %s;", packageName); - newLine(); + @Override + protected @NotNull Set collectClasses(Set imports, String packageName) { Set includedPackages = JBIterable.from(imports) .filter(o -> !o.startsWith("static") && o.endsWith(".*")) .map(o -> StringUtil.trimEnd(o, ".*")) @@ -744,62 +645,12 @@ private void generateClassHeader(String className, if (includedPackages.contains(info.intfPackage)) includedClasses.add(StringUtil.getShortName(info.intfClass)); if (includedPackages.contains(info.implPackage)) includedClasses.add(StringUtil.getShortName(info.implClass)); } - NameShortener shortener = new NameShortener(packageName, !G.generateFQN); - 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 != 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; - } - - 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; + return includedClasses; } - 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; + @NotNull + protected String generatePackageName(String className) { + return StringUtil.getPackageName(className); } /** @@ -859,7 +710,7 @@ void generateNode(BnfRule rule, BnfExpression initialNode, String funcName, Set< String extraParameters = metaParameters.stream().map(it -> ", Parser " + it).collect(joining()); out("%sstatic boolean %s(%s %s, int %s%s) {", !isRule ? "private " : isPrivate ? "" : "public ", - funcName, shorten(PSI_BUILDER_CLASS), N.builder, N.level, extraParameters); + funcName, shorten(C.PsiBuilderClass), N.builder, N.level, extraParameters); if (isSingleNode) { if (isPrivate && !isLeftInner && recoverWhile == null && frameName == null) { String nodeCall = generateNodeCall(rule, node, getNextName(funcName, 0)).render(N); @@ -905,7 +756,7 @@ void generateNode(BnfRule rule, BnfExpression initialNode, String funcName, Set< boolean sectionMaybeDropped = sectionRequiredSimple && type == BNF_CHOICE && elementTypeRef == null && !ContainerUtil.exists(children, o -> isRollbackRequired(o, myFile)); String modifiers = modifierList.isEmpty()? "_NONE_" : StringUtil.join(modifierList, " | "); - String shortMarker = !G.generateFQN ? "Marker" : PSI_BUILDER_CLASS + ".Marker"; + String shortMarker = !G.generateFQN ? "Marker" : C.PsiBuilderClass + ".Marker"; if (sectionRequiredSimple) { if (!sectionMaybeDropped) { out("%s %s = enter_section_(%s);", shortMarker, N.marker, N.builder); @@ -1495,13 +1346,13 @@ private void generateElementTypesHolder(String className, Map s String tokenTypeClass = getRootAttribute(myFile, KnownAttribute.TOKEN_TYPE_CLASS); String tokenTypeFactory = getRootAttribute(myFile, KnownAttribute.TOKEN_TYPE_FACTORY); Set imports = new LinkedHashSet<>(); - imports.add(IELEMENTTYPE_CLASS); + imports.add(C.IElementTypeClass); if (G.generatePsi) { - imports.add(PSI_ELEMENT_CLASS); - imports.add(AST_NODE_CLASS); + imports.add(C.PsiElementClass); + imports.add(C.AstNodeClass); } if (G.generateTokenSets && !myTokenSets.isEmpty()) { - imports.add(TOKEN_SET_CLASS); + imports.add(C.TokenSetClass); } boolean useExactElements = "all".equals(G.generateExactTypes) || G.generateExactTypes.contains("elements"); boolean useExactTokens = "all".equals(G.generateExactTypes) || G.generateExactTypes.contains("tokens"); @@ -1513,14 +1364,14 @@ private void generateElementTypesHolder(String className, Map s String elementTypeClass = getAttribute(rule, KnownAttribute.ELEMENT_TYPE_CLASS); String elementTypeFactory = getAttribute(rule, KnownAttribute.ELEMENT_TYPE_FACTORY); compositeToClassAndFactoryMap.put(elementType, Trinity.create(elementTypeClass, elementTypeFactory, ruleInfo)); - if (elementTypeFactory != null) { + if (useFactory(elementTypeFactory)) { imports.add(StringUtil.getPackageName(elementTypeFactory)); } else { ContainerUtil.addIfNotNull(imports, elementTypeClass); } } - if (tokenTypeFactory != null) { + if (useFactory(tokenTypeFactory)) { imports.add(StringUtil.getPackageName(tokenTypeFactory)); } else { @@ -1545,13 +1396,13 @@ private void generateElementTypesHolder(String className, Map s for (String elementType : sortedCompositeTypes.keySet()) { Trinity info = compositeToClassAndFactoryMap.get(elementType); String elementCreateCall; - if (info.second == null) { + if (!useFactory(info.second)) { elementCreateCall = "new " + shorten(info.first); } else { elementCreateCall = shorten(StringUtil.getPackageName(info.second)) + "." + StringUtil.getShortName(info.second); } - String fieldType = useExactElements && info.first != null ? info.first : IELEMENTTYPE_CLASS; + String fieldType = useExactElements && info.first != null ? info.first : C.IElementTypeClass; String callFix = elementCreateCall.endsWith("IElementType") ? ", null" : ""; out("%s %s = %s(\"%s\"%s);", shorten(fieldType), elementType, elementCreateCall, elementType, callFix); } @@ -1561,14 +1412,14 @@ private void generateElementTypesHolder(String className, Map s String exactType = null; Map sortedTokens = new TreeMap<>(); String tokenCreateCall; - if (tokenTypeFactory == null) { + if (!useFactory(tokenTypeFactory)) { exactType = tokenTypeClass; tokenCreateCall = "new " + shorten(exactType); } else { tokenCreateCall = shorten(StringUtil.getPackageName(tokenTypeFactory)) + "." + StringUtil.getShortName(tokenTypeFactory); } - String fieldType = ObjectUtils.notNull(useExactTokens ? exactType : null, IELEMENTTYPE_CLASS); + String fieldType = ObjectUtils.notNull(useExactTokens ? exactType : null, C.IElementTypeClass); for (String tokenText : mySimpleTokens.keySet()) { String tokenName = ObjectUtils.chooseNotNull(mySimpleTokens.get(tokenText), tokenText); if (isIgnoredWhitespaceToken(tokenName, tokenText)) continue; @@ -1583,7 +1434,7 @@ private void generateElementTypesHolder(String className, Map s } if (G.generatePsi && G.generatePsiClassesMap) { String shortJC = shorten(CommonClassNames.JAVA_LANG_CLASS); - String shortET = shorten(IELEMENTTYPE_CLASS); + String shortET = shorten(C.IElementTypeClass); newLine(); out("class Classes {"); newLine(); @@ -1595,7 +1446,7 @@ private void generateElementTypesHolder(String className, Map s out("return %s.unmodifiableSet(ourMap.keySet());", shorten(CommonClassNames.JAVA_UTIL_COLLECTIONS)); out("}"); newLine(); - String type = shorten("java.util.LinkedHashMap<" + IELEMENTTYPE_CLASS+ ", java.lang.Class>"); + String type = shorten("java.util.LinkedHashMap<" + C.IElementTypeClass + ", java.lang.Class>"); out("private static final %s ourMap = new %1$s();", type); newLine(); out("static {"); @@ -1621,8 +1472,8 @@ private void generateElementTypesHolder(String className, Map s if (info.isAbstract) continue; if (info.mixedAST) continue; if (first1) { - out("public static %s createElement(%s node) {", shorten(PSI_ELEMENT_CLASS), shorten(AST_NODE_CLASS)); - out("%s type = node.getElementType();", shorten(IELEMENTTYPE_CLASS)); + out("public static %s createElement(%s node) {", shorten(C.PsiElementClass), shorten(C.AstNodeClass)); + out("%s type = node.getElementType();", shorten(C.IElementTypeClass)); } String psiClass = getAttribute(rule, KnownAttribute.PSI_IMPL_PACKAGE) + "." + getRulePsiClassName(rule, myImplClassFormat); out((!first1 ? "else " : "") + "if (type == " + elementType + ") {"); @@ -1643,7 +1494,7 @@ private void generateElementTypesHolder(String className, Map s if (first2) { if (!first1) newLine(); out("public static %s createElement(%s type) {", shorten(COMPOSITE_PSI_ELEMENT_CLASS), - shorten(IELEMENTTYPE_CLASS)); + shorten(C.IElementTypeClass)); } String psiClass = getRulePsiClassName(rule, myImplClassFormat); out((!first2 ? "else" : "") + " if (type == " + elementType + ") {"); @@ -1660,6 +1511,10 @@ private void generateElementTypesHolder(String className, Map s out("}"); } + protected boolean useFactory(String factory) { + return factory != null; + } + private boolean isIgnoredWhitespaceToken(@NotNull String tokenName, @NotNull String tokenText) { return isRegexpToken(tokenText) && !myTokensUsedInGrammar.contains(tokenName) && @@ -1695,7 +1550,7 @@ private void generatePsiIntf(BnfRule rule, RuleInfo info) { Set imports = new LinkedHashSet<>(); imports.addAll(Arrays.asList("java.util.List", "org.jetbrains.annotations.*", - PSI_ELEMENT_CLASS)); + C.PsiElementClass)); imports.addAll(psiSupers); imports.addAll(getRuleMethodTypesToImport(rule)); @@ -1714,8 +1569,8 @@ private void generatePsiImpl(BnfRule rule, RuleInfo info) { if (!G.generateFQN) { imports.addAll(Arrays.asList(CommonClassNames.JAVA_UTIL_LIST, "org.jetbrains.annotations.*", - AST_NODE_CLASS, - PSI_ELEMENT_CLASS)); + C.AstNodeClass, + C.PsiElementClass)); if (myVisitorClassName != null) imports.add(PSI_ELEMENT_VISITOR_CLASS); imports.add(myPsiTreeUtilClass); } @@ -1780,7 +1635,7 @@ private void generatePsiImpl(BnfRule rule, RuleInfo info) { generateClassHeader(psiClass, imports, "", javaType, implSuper, superInterface); String shortName = StringUtil.getShortName(psiClass); if (constructors.isEmpty()) { - out("public " + shortName + "(" + shorten(AST_NODE_CLASS) + " node) {"); + out("public " + shortName + "(" + shorten(C.AstNodeClass) + " node) {"); out("super(node);"); out("}"); newLine(); @@ -1797,8 +1652,8 @@ private void generatePsiImpl(BnfRule rule, RuleInfo info) { for (NavigatablePsiElement m : constructors) { List types = myJavaHelper.getMethodTypes(m); Function> annoProvider = i -> myJavaHelper.getParameterAnnotations(m, (i - 1) / 2); - out("public " + shortName + "(" + getParametersString(types, 1, 3, substitutor, annoProvider, myShortener) + ") {"); - out("super(" + getParametersString(types, 1, 2, substitutor, annoProvider, myShortener) + ");"); + out("public " + shortName + "(" + getParametersString(types, 1, 3, substitutor, annoProvider, getShortener()) + ") {"); + out("super(" + getParametersString(types, 1, 2, substitutor, annoProvider, getShortener()) + ");"); out("}"); newLine(); } @@ -1959,7 +1814,7 @@ else if (type == OPTIONAL) { else { out(shorten(NOTNULL_ANNO)); } - String s = isToken ? PSI_ELEMENT_CLASS : getAccessorType(methodInfo.rule); + String s = isToken ? C.PsiElementClass : getAccessorType(methodInfo.rule); String className = shorten(s); String tail = intf ? "();" : "() {"; out((intf ? "" : "public ") + (many ? shorten(CommonClassNames.JAVA_UTIL_LIST) + "<" : "") + className + (many ? "> " : " ") + getterName + tail); @@ -2089,7 +1944,7 @@ else if (i > 0 && StringUtil.isEmpty(targetInfo.name)) { } boolean many = targetInfo.cardinality.many(); - String className = shorten(targetInfo.rule == null ? PSI_ELEMENT_CLASS : getAccessorType(targetInfo.rule)); + String className = shorten(targetInfo.rule == null ? C.PsiElementClass : getAccessorType(targetInfo.rule)); String type = (many ? shorten(CommonClassNames.JAVA_UTIL_LIST) + "<" : "") + className + (many ? "> " : " "); String curId = N.psiLocal + (count++); @@ -2162,7 +2017,7 @@ else if (i > 0 && StringUtil.isEmpty(targetInfo.name)) { } boolean many = cardinality.many(); - String s = targetRule == null ? PSI_ELEMENT_CLASS : getAccessorType(targetRule); + String s = targetRule == null ? C.PsiElementClass : getAccessorType(targetRule); String className = shorten(s); String getterName = getGetterName(methodInfo.name); String tail = intf ? "();" : "() {"; @@ -2200,15 +2055,15 @@ private void generateUtilMethod(String methodName, Function substitutor = ParserGeneratorUtil::unwrapTypeArgumentForParamList; out("%s%s%s %s(%s)%s%s", intf ? "" : "public ", - getGenericClauseString(genericParameters, myShortener), + getGenericClauseString(genericParameters, getShortener()), returnType, methodName, - getParametersString(methodTypes, offset, 3, substitutor, annoProvider, myShortener), - getThrowsString(exceptionList, myShortener), + getParametersString(methodTypes, offset, 3, substitutor, annoProvider, getShortener()), + getThrowsString(exceptionList, getShortener()), intf ? ";" : " {"); if (!intf) { String implUtilRef = shorten(StringUtil.notNullize(myPsiImplUtilClass, KnownAttribute.PSI_IMPL_UTIL_CLASS.getName())); - String string = getParametersString(methodTypes, offset, 2, substitutor, annoProvider, myShortener); + String string = getParametersString(methodTypes, offset, 2, substitutor, annoProvider, getShortener()); out("%s%s.%s(this%s);", "void".equals(returnType) ? "" : "return ", implUtilRef, methodName, string.isEmpty() ? "" : ", " + string); out("}"); diff --git a/src/org/intellij/grammar/psi/impl/BnfFileImpl.java b/src/org/intellij/grammar/psi/impl/BnfFileImpl.java index 45a6e83b..c1a39ea1 100755 --- a/src/org/intellij/grammar/psi/impl/BnfFileImpl.java +++ b/src/org/intellij/grammar/psi/impl/BnfFileImpl.java @@ -191,7 +191,11 @@ private Map> calcAttributeValues() { return result; } - private static @NotNull AtomicClearableLazyValue lazyValue(Supplier producer) { + protected boolean hasAttributeValue(@Nullable BnfRule rule, @NotNull KnownAttribute attribute, @Nullable String match) { + return getMatchingAttributes(rule, attribute, match).isNotEmpty(); + } + + protected static @NotNull AtomicClearableLazyValue lazyValue(Supplier producer) { return new AtomicClearableLazyValue<>() { @Override protected @NotNull T compute() { diff --git a/testData/fleet/FleetExprParser.PSI.expected.java b/testData/fleet/FleetExprParser.PSI.expected.java new file mode 100644 index 00000000..1ef4eaf7 --- /dev/null +++ b/testData/fleet/FleetExprParser.PSI.expected.java @@ -0,0 +1,42 @@ +// ---- ExpressionTypes.java ----------------- +//header.txt +package fleet.org.intellij.grammar.expression; + +import fleet.com.intellij.psi.tree.IElementType; + +public interface ExpressionTypes { + + IElementType ARG_LIST = new IElementType("ARG_LIST", null); + IElementType ASSIGN_EXPR = new IElementType("ASSIGN_EXPR", null); + IElementType BETWEEN_EXPR = new IElementType("BETWEEN_EXPR", null); + IElementType CALL_EXPR = new IElementType("CALL_EXPR", null); + IElementType CONDITIONAL_EXPR = new IElementType("CONDITIONAL_EXPR", null); + IElementType DIV_EXPR = new IElementType("DIV_EXPR", null); + IElementType ELVIS_EXPR = new IElementType("ELVIS_EXPR", null); + IElementType EXPR = new IElementType("EXPR", null); + IElementType EXP_EXPR = new IElementType("EXP_EXPR", null); + IElementType FACTORIAL_EXPR = new IElementType("FACTORIAL_EXPR", null); + IElementType IDENTIFIER = new IElementType("IDENTIFIER", null); + IElementType IS_NOT_EXPR = new IElementType("IS_NOT_EXPR", null); + IElementType LITERAL_EXPR = new IElementType("LITERAL_EXPR", null); + IElementType MINUS_EXPR = new IElementType("MINUS_EXPR", null); + IElementType MUL_EXPR = new IElementType("MUL_EXPR", null); + IElementType PAREN_EXPR = new IElementType("PAREN_EXPR", null); + IElementType PLUS_EXPR = new IElementType("PLUS_EXPR", null); + IElementType REF_EXPR = new IElementType("REF_EXPR", null); + IElementType SPECIAL_EXPR = new IElementType("SPECIAL_EXPR", null); + IElementType UNARY_MIN_EXPR = new IElementType("UNARY_MIN_EXPR", null); + IElementType UNARY_NOT_EXPR = new IElementType("UNARY_NOT_EXPR", null); + IElementType UNARY_PLUS_EXPR = new IElementType("UNARY_PLUS_EXPR", null); + IElementType XOR_EXPR = new IElementType("XOR_EXPR", null); + + IElementType AND = new IElementType("AND", null); + IElementType BETWEEN = new IElementType("BETWEEN", null); + IElementType COMMENT = new IElementType("comment", null); + IElementType ID = new IElementType("id", null); + IElementType IS = new IElementType("IS", null); + IElementType NOT = new IElementType("NOT", null); + IElementType NUMBER = new IElementType("number", null); + IElementType STRING = new IElementType("string", null); + IElementType SYNTAX = new IElementType("syntax", null); +} \ No newline at end of file diff --git a/testData/fleet/FleetExprParser.bnf b/testData/fleet/FleetExprParser.bnf new file mode 100644 index 00000000..e32730a1 --- /dev/null +++ b/testData/fleet/FleetExprParser.bnf @@ -0,0 +1,79 @@ +/* + * 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. + */ + +{ + generate=[psi="no"] + classHeader="//header.txt" + parserClass="org.intellij.grammar.expression.ExpressionParser" + extends(".*expr")=expr + elementTypeFactory="org.intellij.grammar.expression.ExpressionParserDefinition.createType" + tokenTypeFactory="org.intellij.grammar.expression.ExpressionParserDefinition.createTokenType" + elementTypeHolderClass="org.intellij.grammar.expression.ExpressionTypes" + parserUtilClass="org.intellij.grammar.parser.GeneratedParserUtilBase" + + tokens=[ + space='regexp:\s+' + comment='regexp://.*' + number='regexp:\d+(\.\d*)?' + id='regexp:\p{Alpha}\w*' + string="regexp:('([^'\\]|\\.)*'|\"([^\"\\]|\\.)*\")" + + syntax='regexp:;|\.|\+|-|\*\*|\*|==|=|/|,|\(|\)|\^|\!=|\!|>=|<=|>|<' + ] +} +root ::= element * +private element ::= expr ';'? {recoverWhile=element_recover} +private element_recover ::= !('(' | '+' | '-' | '!' | 'multiply' | id | number) + +// left recursion and empty PSI children define expression root +expr ::= assign_expr + | conditional_group + | add_group + | boolean_group + | mul_group + | unary_group + | exp_expr + | factorial_expr + | call_expr + | qualification_expr + | primary_group + {extraRoot=true} +private boolean_group ::= xor_expr | between_expr | is_not_expr + +private conditional_group ::= elvis_expr | conditional_expr +private unary_group ::= unary_plus_expr | unary_min_expr | unary_not_expr +private mul_group ::= mul_expr | div_expr +private add_group ::= plus_expr | minus_expr +private primary_group ::= special_expr | simple_ref_expr | literal_expr | paren_expr + +// expressions: auto-operator detection or parens +fake ref_expr ::= expr? '.' identifier +simple_ref_expr ::= identifier {extends=ref_expr elementType=ref_expr} +qualification_expr ::= expr '.' identifier {extends=ref_expr elementType=ref_expr} +call_expr ::= ref_expr arg_list +arg_list ::= '(' [ !')' expr (',' expr) * ] ')' {pin(".*")=1} +literal_expr ::= number +identifier ::= id +unary_min_expr ::= '-' expr +unary_plus_expr ::= '+' expr +unary_not_expr ::= '!' expr +xor_expr ::= expr '^' expr +assign_expr ::= expr '=' expr { rightAssociative=true } +conditional_expr ::= expr ('<' | '>' | '<=' | '>=' | '==' | '!=') expr +div_expr ::= expr '/' expr +mul_expr ::= expr '*' expr +minus_expr ::= expr '-' expr +plus_expr ::= expr '+' expr +exp_expr ::= expr ('**' expr) + // N-ary variant +factorial_expr ::= expr '!' +paren_expr ::= '(' expr ')' +elvis_expr ::= expr '?' expr ':' expr +is_not_expr ::= expr IS NOT expr +between_expr ::= expr BETWEEN add_group AND add_group { + methods=[testExpr="expr[0]"] +} + +// test specific expressions +external special_expr ::= meta_special_expr +meta_special_expr ::= 'multiply' '(' simple_ref_expr ',' mul_expr ')' {elementType="special_expr" pin=2} diff --git a/testData/fleet/FleetExprParser.expected.java b/testData/fleet/FleetExprParser.expected.java new file mode 100644 index 00000000..f8460ee1 --- /dev/null +++ b/testData/fleet/FleetExprParser.expected.java @@ -0,0 +1,426 @@ +// ---- ExpressionParser.java ----------------- +//header.txt +package fleet.org.intellij.grammar.expression; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.org.intellij.grammar.expression.ExpressionTypes.*; +import static fleet.org.intellij.grammar.parser.GeneratedParserUtilBase.*; +import fleet.com.intellij.psi.tree.IElementType; +import fleet.com.intellij.lang.ASTNode; +import fleet.com.intellij.psi.tree.TokenSet; +import fleet.com.intellij.lang.PsiParser; +import fleet.com.intellij.lang.LightPsiParser; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class ExpressionParser implements PsiParser, LightPsiParser { + + public ASTNode parse(IElementType root_, PsiBuilder builder_) { + parseLight(root_, builder_); + return builder_.getTreeBuilt(); + } + + public void parseLight(IElementType root_, PsiBuilder builder_) { + boolean result_; + builder_ = adapt_builder_(root_, builder_, this, EXTENDS_SETS_); + Marker marker_ = enter_section_(builder_, 0, _COLLAPSE_, null); + result_ = parse_root_(root_, builder_); + exit_section_(builder_, 0, marker_, root_, result_, true, TRUE_CONDITION); + } + + protected boolean parse_root_(IElementType root_, PsiBuilder builder_) { + return parse_root_(root_, builder_, 0); + } + + static boolean parse_root_(IElementType root_, PsiBuilder builder_, int level_) { + boolean result_; + if (root_ == EXPR) { + result_ = expr(builder_, level_ + 1, -1); + } + else { + result_ = root(builder_, level_ + 1); + } + return result_; + } + + public static final TokenSet[] EXTENDS_SETS_ = new TokenSet[] { + create_token_set_(ASSIGN_EXPR, BETWEEN_EXPR, CALL_EXPR, CONDITIONAL_EXPR, + DIV_EXPR, ELVIS_EXPR, EXPR, EXP_EXPR, + FACTORIAL_EXPR, IS_NOT_EXPR, LITERAL_EXPR, MINUS_EXPR, + MUL_EXPR, PAREN_EXPR, PLUS_EXPR, REF_EXPR, + SPECIAL_EXPR, UNARY_MIN_EXPR, UNARY_NOT_EXPR, UNARY_PLUS_EXPR, + XOR_EXPR), + }; + + /* ********************************************************** */ + // '(' [ !')' expr (',' expr) * ] ')' + public static boolean arg_list(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "arg_list")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ARG_LIST, ""); + result_ = consumeToken(builder_, "("); + pinned_ = result_; // pin = 1 + result_ = result_ && report_error_(builder_, arg_list_1(builder_, level_ + 1)); + result_ = pinned_ && consumeToken(builder_, ")") && result_; + exit_section_(builder_, level_, marker_, result_, pinned_, null); + return result_ || pinned_; + } + + // [ !')' expr (',' expr) * ] + private static boolean arg_list_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "arg_list_1")) return false; + arg_list_1_0(builder_, level_ + 1); + return true; + } + + // !')' expr (',' expr) * + private static boolean arg_list_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "arg_list_1_0")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = arg_list_1_0_0(builder_, level_ + 1); + pinned_ = result_; // pin = 1 + result_ = result_ && report_error_(builder_, expr(builder_, level_ + 1, -1)); + result_ = pinned_ && arg_list_1_0_2(builder_, level_ + 1) && result_; + exit_section_(builder_, level_, marker_, result_, pinned_, null); + return result_ || pinned_; + } + + // !')' + private static boolean arg_list_1_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "arg_list_1_0_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NOT_); + result_ = !consumeToken(builder_, ")"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // (',' expr) * + private static boolean arg_list_1_0_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "arg_list_1_0_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!arg_list_1_0_2_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "arg_list_1_0_2", pos_)) break; + } + return true; + } + + // ',' expr + private static boolean arg_list_1_0_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "arg_list_1_0_2_0")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = consumeToken(builder_, ","); + pinned_ = result_; // pin = 1 + result_ = result_ && expr(builder_, level_ + 1, -1); + exit_section_(builder_, level_, marker_, result_, pinned_, null); + return result_ || pinned_; + } + + /* ********************************************************** */ + // expr ';'? + static boolean element(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "element")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = expr(builder_, level_ + 1, -1); + result_ = result_ && element_1(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, ExpressionParser::element_recover); + return result_; + } + + // ';'? + private static boolean element_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "element_1")) return false; + consumeToken(builder_, ";"); + return true; + } + + /* ********************************************************** */ + // !('(' | '+' | '-' | '!' | 'multiply' | id | number) + static boolean element_recover(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "element_recover")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NOT_); + result_ = !element_recover_0(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // '(' | '+' | '-' | '!' | 'multiply' | id | number + private static boolean element_recover_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "element_recover_0")) return false; + boolean result_; + result_ = consumeToken(builder_, "("); + if (!result_) result_ = consumeToken(builder_, "+"); + if (!result_) result_ = consumeToken(builder_, "-"); + if (!result_) result_ = consumeToken(builder_, "!"); + if (!result_) result_ = consumeToken(builder_, "multiply"); + if (!result_) result_ = consumeToken(builder_, ID); + if (!result_) result_ = consumeToken(builder_, NUMBER); + return result_; + } + + /* ********************************************************** */ + // id + public static boolean identifier(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "identifier")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ID); + exit_section_(builder_, marker_, IDENTIFIER, result_); + return result_; + } + + /* ********************************************************** */ + // 'multiply' '(' simple_ref_expr ',' mul_expr ')' + public static boolean meta_special_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "meta_special_expr")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, SPECIAL_EXPR, ""); + result_ = consumeToken(builder_, "multiply"); + result_ = result_ && consumeToken(builder_, "("); + pinned_ = result_; // pin = 2 + result_ = result_ && report_error_(builder_, simple_ref_expr(builder_, level_ + 1)); + result_ = pinned_ && report_error_(builder_, consumeToken(builder_, ",")) && result_; + result_ = pinned_ && report_error_(builder_, expr(builder_, level_ + 1, 3)) && result_; + result_ = pinned_ && consumeToken(builder_, ")") && result_; + exit_section_(builder_, level_, marker_, result_, pinned_, null); + return result_ || pinned_; + } + + /* ********************************************************** */ + // element * + static boolean root(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "root")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!element(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "root", pos_)) break; + } + return true; + } + + /* ********************************************************** */ + // Expression root: expr + // Operator priority table: + // 0: BINARY(assign_expr) + // 1: BINARY(elvis_expr) BINARY(conditional_expr) + // 2: BINARY(plus_expr) BINARY(minus_expr) + // 3: BINARY(xor_expr) BINARY(between_expr) BINARY(is_not_expr) + // 4: BINARY(mul_expr) BINARY(div_expr) + // 5: PREFIX(unary_plus_expr) PREFIX(unary_min_expr) PREFIX(unary_not_expr) + // 6: N_ARY(exp_expr) + // 7: POSTFIX(factorial_expr) + // 8: POSTFIX(call_expr) + // 9: POSTFIX(qualification_expr) + // 10: ATOM(special_expr) ATOM(simple_ref_expr) ATOM(literal_expr) PREFIX(paren_expr) + public static boolean expr(PsiBuilder builder_, int level_, int priority_) { + if (!recursion_guard_(builder_, level_, "expr")) return false; + addVariant(builder_, ""); + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ""); + result_ = unary_plus_expr(builder_, level_ + 1); + if (!result_) result_ = unary_min_expr(builder_, level_ + 1); + if (!result_) result_ = unary_not_expr(builder_, level_ + 1); + if (!result_) result_ = meta_special_expr(builder_, level_ + 1); + if (!result_) result_ = simple_ref_expr(builder_, level_ + 1); + if (!result_) result_ = literal_expr(builder_, level_ + 1); + if (!result_) result_ = paren_expr(builder_, level_ + 1); + pinned_ = result_; + result_ = result_ && expr_0(builder_, level_ + 1, priority_); + exit_section_(builder_, level_, marker_, null, result_, pinned_, null); + return result_ || pinned_; + } + + public static boolean expr_0(PsiBuilder builder_, int level_, int priority_) { + if (!recursion_guard_(builder_, level_, "expr_0")) return false; + boolean result_ = true; + while (true) { + Marker marker_ = enter_section_(builder_, level_, _LEFT_, null); + if (priority_ < 0 && consumeTokenSmart(builder_, "=")) { + result_ = expr(builder_, level_, -1); + exit_section_(builder_, level_, marker_, ASSIGN_EXPR, result_, true, null); + } + else if (priority_ < 1 && consumeTokenSmart(builder_, "?")) { + result_ = report_error_(builder_, expr(builder_, level_, 1)); + result_ = elvis_expr_1(builder_, level_ + 1) && result_; + exit_section_(builder_, level_, marker_, ELVIS_EXPR, result_, true, null); + } + else if (priority_ < 1 && conditional_expr_0(builder_, level_ + 1)) { + result_ = expr(builder_, level_, 1); + exit_section_(builder_, level_, marker_, CONDITIONAL_EXPR, result_, true, null); + } + else if (priority_ < 2 && consumeTokenSmart(builder_, "+")) { + result_ = expr(builder_, level_, 2); + exit_section_(builder_, level_, marker_, PLUS_EXPR, result_, true, null); + } + else if (priority_ < 2 && consumeTokenSmart(builder_, "-")) { + result_ = expr(builder_, level_, 2); + exit_section_(builder_, level_, marker_, MINUS_EXPR, result_, true, null); + } + else if (priority_ < 3 && consumeTokenSmart(builder_, "^")) { + result_ = expr(builder_, level_, 3); + exit_section_(builder_, level_, marker_, XOR_EXPR, result_, true, null); + } + else if (priority_ < 3 && consumeTokenSmart(builder_, BETWEEN)) { + result_ = report_error_(builder_, expr(builder_, level_, 1)); + result_ = between_expr_1(builder_, level_ + 1) && result_; + exit_section_(builder_, level_, marker_, BETWEEN_EXPR, result_, true, null); + } + else if (priority_ < 3 && parseTokensSmart(builder_, 0, IS, NOT)) { + result_ = expr(builder_, level_, 3); + exit_section_(builder_, level_, marker_, IS_NOT_EXPR, result_, true, null); + } + else if (priority_ < 4 && consumeTokenSmart(builder_, "*")) { + result_ = expr(builder_, level_, 4); + exit_section_(builder_, level_, marker_, MUL_EXPR, result_, true, null); + } + else if (priority_ < 4 && consumeTokenSmart(builder_, "/")) { + result_ = expr(builder_, level_, 4); + exit_section_(builder_, level_, marker_, DIV_EXPR, result_, true, null); + } + else if (priority_ < 7 && consumeTokenSmart(builder_, "!")) { + result_ = true; + exit_section_(builder_, level_, marker_, FACTORIAL_EXPR, result_, true, null); + } + else if (priority_ < 6 && consumeTokenSmart(builder_, "**")) { + while (true) { + result_ = report_error_(builder_, expr(builder_, level_, 6)); + if (!consumeTokenSmart(builder_, "**")) break; + } + exit_section_(builder_, level_, marker_, EXP_EXPR, result_, true, null); + } + else if (priority_ < 8 && leftMarkerIs(builder_, REF_EXPR) && arg_list(builder_, level_ + 1)) { + result_ = true; + exit_section_(builder_, level_, marker_, CALL_EXPR, result_, true, null); + } + else if (priority_ < 9 && qualification_expr_0(builder_, level_ + 1)) { + result_ = true; + exit_section_(builder_, level_, marker_, REF_EXPR, result_, true, null); + } + else { + exit_section_(builder_, level_, marker_, null, false, false, null); + break; + } + } + return result_; + } + + // ':' expr + private static boolean elvis_expr_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "elvis_expr_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ":"); + result_ = result_ && expr(builder_, level_ + 1, -1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // '<' | '>' | '<=' | '>=' | '==' | '!=' + private static boolean conditional_expr_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "conditional_expr_0")) return false; + boolean result_; + result_ = consumeTokenSmart(builder_, "<"); + if (!result_) result_ = consumeTokenSmart(builder_, ">"); + if (!result_) result_ = consumeTokenSmart(builder_, "<="); + if (!result_) result_ = consumeTokenSmart(builder_, ">="); + if (!result_) result_ = consumeTokenSmart(builder_, "=="); + if (!result_) result_ = consumeTokenSmart(builder_, "!="); + return result_; + } + + public static boolean unary_plus_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "unary_plus_expr")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, null); + result_ = consumeTokenSmart(builder_, "+"); + pinned_ = result_; + result_ = pinned_ && expr(builder_, level_, 5); + exit_section_(builder_, level_, marker_, UNARY_PLUS_EXPR, result_, pinned_, null); + return result_ || pinned_; + } + + public static boolean unary_min_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "unary_min_expr")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, null); + result_ = consumeTokenSmart(builder_, "-"); + pinned_ = result_; + result_ = pinned_ && expr(builder_, level_, 5); + exit_section_(builder_, level_, marker_, UNARY_MIN_EXPR, result_, pinned_, null); + return result_ || pinned_; + } + + // AND add_group + private static boolean between_expr_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "between_expr_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, AND); + result_ = result_ && expr(builder_, level_ + 1, 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + public static boolean unary_not_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "unary_not_expr")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, null); + result_ = consumeTokenSmart(builder_, "!"); + pinned_ = result_; + result_ = pinned_ && expr(builder_, level_, 5); + exit_section_(builder_, level_, marker_, UNARY_NOT_EXPR, result_, pinned_, null); + return result_ || pinned_; + } + + // '.' identifier + private static boolean qualification_expr_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "qualification_expr_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeTokenSmart(builder_, "."); + result_ = result_ && identifier(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // identifier + public static boolean simple_ref_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "simple_ref_expr")) return false; + if (!nextTokenIsSmart(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = identifier(builder_, level_ + 1); + exit_section_(builder_, marker_, REF_EXPR, result_); + return result_; + } + + // number + public static boolean literal_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "literal_expr")) return false; + if (!nextTokenIsSmart(builder_, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeTokenSmart(builder_, NUMBER); + exit_section_(builder_, marker_, LITERAL_EXPR, result_); + return result_; + } + + public static boolean paren_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "paren_expr")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, null); + result_ = consumeTokenSmart(builder_, "("); + pinned_ = result_; + result_ = pinned_ && expr(builder_, level_, -1); + result_ = pinned_ && report_error_(builder_, consumeToken(builder_, ")")) && result_; + exit_section_(builder_, level_, marker_, PAREN_EXPR, result_, pinned_, null); + return result_ || pinned_; + } + +} \ No newline at end of file diff --git a/testData/fleet/FleetExternalRules.bnf b/testData/fleet/FleetExternalRules.bnf new file mode 100644 index 00000000..8b63fbff --- /dev/null +++ b/testData/fleet/FleetExternalRules.bnf @@ -0,0 +1,103 @@ +/* + * 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. + */ + +{ + parserClass="FleetExternalRules" + generatePsi=false + parserUtilClass="org.intellij.grammar.test.ParserUtil" + tokens=[ + perc='%' + paren1='(' + paren2=')' + comma=',' + perc_re='regexp:\%' + ] +} + +// external parsing +root ::= <> +external ref ::= parseRef +external unique_list_of ::= uniqueListOf +external unique_list_of_params ::= uniqueListOf <> "1+1" <> '1+1' +external empty_external ::= + +one ::= 'one' +two ::= 'two' +statement ::= one | two + +private perc_list ::= <> +private perc_re_list1 ::= <> +private perc_re_list2 ::= <> + +private param_seq ::= '{' <> '}' +private param_choice ::= '{' <> '}' +private param_opt ::= '{' <> '}' +private param_choice_alt ::= '{' <> '}' +private param_seq_alt ::= '{' <> '}' +private param_seq_alt_ext ::= '{' <> '}' +private param_seq_alt_params_ext ::= '{' <> '}' + +// meta rules +meta comma_list ::= <> (',' <>) * +meta comma_list_pinned ::= <> <> (<>>>) * +meta comma_list_tail ::= ',' <> {pin=1} +meta list_of_lists ::= <> <>>> (<>>>>>) * +meta meta_multi_level ::= <>>>>>>>>>>> +meta meta_multi_level_pinned ::= <> <>>>>>>>>>>> +meta meta_multi_level_pinned_paren ::= <> (<>>>>>>>)>>>> +meta meta_with_in_place ::= <> | some)>> + +private meta_simple ::= <> +private meta_seq ::= <> +private meta_seq_of_lists ::= <> +private multi_level ::= <> +private meta_seq_of_lists_opt ::= (<>)? +private meta_multi_level_no_closure ::= <>>>>> + +private meta comma_paren_list ::= '(' <> (',' <>) * ')' {pin=1} +public_paren_list ::= <> +public_paren_list2 ::= {elementType=public_paren_list} + +// mixed +private meta meta_mixed ::= <>>> +private meta_mixed_simple ::= <> +private meta_mixed_list ::= <>>> +private meta_mixed_list_paren ::= <>)>> + +private meta recoverable_item ::= <> {recoverWhile="item_recover"} +private item_recover ::= !(',' | ';' | ')') + +private meta recoverable_item2 ::= <> {recoverWhile="<>"} +private meta recoverable_item3 ::= <> <> {pin=1 recoverWhile="<>"} + +// use _COLLAPSE_ flag due unknown external rule +collapse_one ::= <> +collapse_two ::= {extends=collapse_one} + +private empty_external_usage ::= empty_external +private empty_external_usage2 ::= <<>> + +meta two_params_meta ::= <> <> +private meta nested_meta ::= <> <> <>>>>> +private meta nested_mixed ::= <>>>) perc_re>> + +private meta main_class_meta ::= <

> +private second_class_meta_usage_from_main ::= <>>> + +;{ + parserClass="FleetExternalRules2" +} +private one_list ::= <> +private one_list_par ::= <> + +private meta second_class_meta ::= <> +private main_class_meta_usage_from_second ::= <>>> +private third_class_meta_usage_from_second ::= <>>> +extra_root ::= {extraRoot=true} + +;{ + parserClass="FleetExternalRules3" +} +private meta third_class_meta ::= <> +private second_class_meta_usage_from_third ::= <>>> diff --git a/testData/fleet/FleetExternalRules.expected.java b/testData/fleet/FleetExternalRules.expected.java new file mode 100644 index 00000000..837273ba --- /dev/null +++ b/testData/fleet/FleetExternalRules.expected.java @@ -0,0 +1,889 @@ +// ---- FleetExternalRules.java ----------------- +// This is a generated file. Not intended for manual editing. +package fleet; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.org.intellij.grammar.test.ParserUtil.*; +import fleet.com.intellij.psi.tree.IElementType; +import fleet.com.intellij.lang.ASTNode; +import fleet.com.intellij.psi.tree.TokenSet; +import fleet.com.intellij.lang.PsiParser; +import fleet.com.intellij.lang.LightPsiParser; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class FleetExternalRules implements PsiParser, LightPsiParser { + + public ASTNode parse(IElementType root_, PsiBuilder builder_) { + parseLight(root_, builder_); + return builder_.getTreeBuilt(); + } + + public void parseLight(IElementType root_, PsiBuilder builder_) { + boolean result_; + builder_ = adapt_builder_(root_, builder_, this, EXTENDS_SETS_); + Marker marker_ = enter_section_(builder_, 0, _COLLAPSE_, null); + result_ = parse_root_(root_, builder_); + exit_section_(builder_, 0, marker_, root_, result_, true, TRUE_CONDITION); + } + + protected boolean parse_root_(IElementType root_, PsiBuilder builder_) { + return parse_root_(root_, builder_, 0); + } + + static boolean parse_root_(IElementType root_, PsiBuilder builder_, int level_) { + boolean result_; + if (root_ == EXTRA_ROOT) { + result_ = FleetExternalRules2.extra_root(builder_, level_ + 1); + } + else { + result_ = root(builder_, level_ + 1); + } + return result_; + } + + public static final TokenSet[] EXTENDS_SETS_ = new TokenSet[] { + create_token_set_(COLLAPSE_ONE, COLLAPSE_TWO), + }; + + /* ********************************************************** */ + // <> + public static boolean collapse_one(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "collapse_one")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _COLLAPSE_, COLLAPSE_ONE, ""); + result_ = uniqueListOf(builder_, level_ + 1, FleetExternalRules::one); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + public static boolean collapse_two(PsiBuilder builder_, int level_) { + Marker marker_ = enter_section_(builder_); + exit_section_(builder_, marker_, COLLAPSE_TWO, true); + return true; + } + + /* ********************************************************** */ + static Parser comma_list_$(Parser param) { + return (builder_, level_) -> comma_list(builder_, level_ + 1, param); + } + + // <> (',' <>) * + public static boolean comma_list(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_list")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = param.parse(builder_, level_); + result_ = result_ && comma_list_1(builder_, level_ + 1, param); + exit_section_(builder_, marker_, COMMA_LIST, result_); + return result_; + } + + // (',' <>) * + private static boolean comma_list_1(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_list_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!comma_list_1_0(builder_, level_ + 1, param)) break; + if (!empty_element_parsed_guard_(builder_, "comma_list_1", pos_)) break; + } + return true; + } + + // ',' <> + private static boolean comma_list_1_0(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_list_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, COMMA); + result_ = result_ && param.parse(builder_, level_); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + static Parser comma_list_pinned_$(Parser head, Parser param) { + return (builder_, level_) -> comma_list_pinned(builder_, level_ + 1, head, param); + } + + // <> <> (<>>>) * + public static boolean comma_list_pinned(PsiBuilder builder_, int level_, Parser head, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_list_pinned")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = head.parse(builder_, level_); + result_ = result_ && param.parse(builder_, level_); + result_ = result_ && comma_list_pinned_2(builder_, level_ + 1, param); + exit_section_(builder_, marker_, COMMA_LIST_PINNED, result_); + return result_; + } + + // (<>>>) * + private static boolean comma_list_pinned_2(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_list_pinned_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!comma_list_pinned_2_0(builder_, level_ + 1, param)) break; + if (!empty_element_parsed_guard_(builder_, "comma_list_pinned_2", pos_)) break; + } + return true; + } + + // <>>> + private static boolean comma_list_pinned_2_0(PsiBuilder builder_, int level_, Parser param) { + return comma_list_tail(builder_, level_ + 1, param); + } + + /* ********************************************************** */ + // ',' <> + public static boolean comma_list_tail(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_list_tail")) return false; + if (!nextTokenIs(builder_, COMMA)) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, COMMA_LIST_TAIL, null); + result_ = consumeToken(builder_, COMMA); + pinned_ = result_; // pin = 1 + result_ = result_ && param.parse(builder_, level_); + exit_section_(builder_, level_, marker_, result_, pinned_, null); + return result_ || pinned_; + } + + /* ********************************************************** */ + // '(' <> (',' <>) * ')' + static boolean comma_paren_list(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_paren_list")) return false; + if (!nextTokenIs(builder_, PAREN1)) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = consumeToken(builder_, PAREN1); + pinned_ = result_; // pin = 1 + result_ = result_ && report_error_(builder_, param.parse(builder_, level_)); + result_ = pinned_ && report_error_(builder_, comma_paren_list_2(builder_, level_ + 1, param)) && result_; + result_ = pinned_ && consumeToken(builder_, PAREN2) && result_; + exit_section_(builder_, level_, marker_, result_, pinned_, null); + return result_ || pinned_; + } + + // (',' <>) * + private static boolean comma_paren_list_2(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_paren_list_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!comma_paren_list_2_0(builder_, level_ + 1, param)) break; + if (!empty_element_parsed_guard_(builder_, "comma_paren_list_2", pos_)) break; + } + return true; + } + + // ',' <> + private static boolean comma_paren_list_2_0(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "comma_paren_list_2_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, COMMA); + result_ = result_ && param.parse(builder_, level_); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // empty_external + static boolean empty_external_usage(PsiBuilder builder_, int level_) { + return null(builder_, level_ + 1); + } + + /* ********************************************************** */ + // <<>> + static boolean empty_external_usage2(PsiBuilder builder_, int level_) { + return true; + } + + /* ********************************************************** */ + // !(',' | ';' | ')') + static boolean item_recover(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "item_recover")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NOT_); + result_ = !item_recover_0(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // ',' | ';' | ')' + private static boolean item_recover_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "item_recover_0")) return false; + boolean result_; + result_ = consumeToken(builder_, COMMA); + if (!result_) result_ = consumeToken(builder_, ";"); + if (!result_) result_ = consumeToken(builder_, PAREN2); + return result_; + } + + /* ********************************************************** */ + // <> <>>> (<>>>>>) * + public static boolean list_of_lists(PsiBuilder builder_, int level_, Parser head, Parser param) { + if (!recursion_guard_(builder_, level_, "list_of_lists")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = head.parse(builder_, level_); + result_ = result_ && comma_list(builder_, level_ + 1, param); + result_ = result_ && list_of_lists_2(builder_, level_ + 1, param); + exit_section_(builder_, marker_, LIST_OF_LISTS, result_); + return result_; + } + + // (<>>>>>) * + private static boolean list_of_lists_2(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "list_of_lists_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!list_of_lists_2_0(builder_, level_ + 1, param)) break; + if (!empty_element_parsed_guard_(builder_, "list_of_lists_2", pos_)) break; + } + return true; + } + + // <>>>>> + private static boolean list_of_lists_2_0(PsiBuilder builder_, int level_, Parser param) { + return comma_list_tail(builder_, level_ + 1, comma_list_$(param)); + } + + /* ********************************************************** */ + static Parser main_class_meta_$(Parser p) { + return (builder_, level_) -> main_class_meta(builder_, level_ + 1, p); + } + + // <

> + +grammar_element ::= expr | external_type3 +expr ::= a_expr (',' a_expr) * {methods=[kids="expr"]} +private a_expr ::= b_expr plus_expr * +left plus_expr ::= '+' expr {extends="expr"} +private b_expr ::= id_expr mul_expr * +left mul_expr ::= '*' expr {extends="expr"} +private id_expr ::= specialRef | reference | literal | external_type | external_type2 +external_type ::= number {elementType="missing_external_type" extends="expr"} +external_type2 ::= id {elementType="id_expr" extends="expr"} +external_same_as_type2 ::= id {elementType="id_expr" extends="expr"} +external_type3 ::= id {elementType="expr"} // do not generate anything +include-section ::= id number include-section {recoverWhile=} + ::= id number {recoverWhile=} +private ::= !() +fake other_expr ::= expr + + +;{ + parserClass="FleetPsiGen2" +} + +meta blockOf ::= <

> + +private reference ::= ref_expr qref_expr * +ref_expr ::= identifier {extends="expr" mixin="MyRefImpl" implements="MyRef"} +left qref_expr ::= '.' identifier {extends="ref_expr" elementType="ref_expr"} +identifier ::= id + +literal ::= number {extends="expr"} +specialRef ::= identifier OF reference {extends="qref_expr"} // test this kind of 'casting' + +some_expr ::= (a_expr | specialRef b_expr | some_expr_private) (cast_expr) (item_expr) * +private some_expr_private ::= specialRef b_expr +left cast_expr ::= '::' id +left item_expr ::= '[' number ']' + +;{ + parserClass="FleetPsiGenFixes" + extends(".*statement")=statement +} + +LeftShadowTest ::= identifier LeftShadow * +left LeftShadow ::= ',' identifier + +private fixMetaRule ::= <> +publicMethodToCall ::= identifier {elementType=""} + +fake namedElement ::= identifier publicMethodToCall (id (',' id) *) {implements="com.intellij.psi.PsiNameIdentifierOwner"} +fake wrapping_statement ::= statement + +choice_joined ::= literal id '%' | '%' id literal {extends=literal} + +statement ::= &<> (a_statement | b_statement) | !<> (c_statement) +a_statement ::= id | number +b_statement ::= id | number +c_statement ::= id | number \ No newline at end of file diff --git a/testData/fleet/FleetPsiGen.expected.java b/testData/fleet/FleetPsiGen.expected.java new file mode 100644 index 00000000..f25492ab --- /dev/null +++ b/testData/fleet/FleetPsiGen.expected.java @@ -0,0 +1,779 @@ +// ---- FleetPsiGen.java ----------------- +//header.txt +package fleet; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.PsiGenUtil.*; +import fleet.com.intellij.psi.tree.IElementType; +import fleet.com.intellij.lang.ASTNode; +import fleet.com.intellij.psi.tree.TokenSet; +import fleet.com.intellij.lang.PsiParser; +import fleet.com.intellij.lang.LightPsiParser; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class FleetPsiGen implements PsiParser, LightPsiParser { + + public ASTNode parse(IElementType root_, PsiBuilder builder_) { + parseLight(root_, builder_); + return builder_.getTreeBuilt(); + } + + public void parseLight(IElementType root_, PsiBuilder builder_) { + boolean result_; + builder_ = adapt_builder_(root_, builder_, this, EXTENDS_SETS_); + Marker marker_ = enter_section_(builder_, 0, _COLLAPSE_, null); + result_ = parse_root_(root_, builder_); + exit_section_(builder_, 0, marker_, root_, result_, true, TRUE_CONDITION); + } + + protected boolean parse_root_(IElementType root_, PsiBuilder builder_) { + return parse_root_(root_, builder_, 0); + } + + static boolean parse_root_(IElementType root_, PsiBuilder builder_, int level_) { + return grammar_root(builder_, level_ + 1); + } + + public static final TokenSet[] EXTENDS_SETS_ = new TokenSet[] { + create_token_set_(ROOT, ROOT_B, ROOT_C, ROOT_D), + create_token_set_(A_STATEMENT, B_STATEMENT, C_STATEMENT, STATEMENT), + create_token_set_(CAST_EXPR, CHOICE_JOINED, EXPR, ID_EXPR, + ITEM_EXPR, LITERAL, MISSING_EXTERNAL_TYPE, MUL_EXPR, + PLUS_EXPR, REF_EXPR, SOME_EXPR, SPECIAL_REF), + }; + + /* ********************************************************** */ + // b_expr plus_expr * + static boolean a_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "a_expr")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = b_expr(builder_, level_ + 1); + result_ = result_ && a_expr_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // plus_expr * + private static boolean a_expr_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "a_expr_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!plus_expr(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "a_expr_1", pos_)) break; + } + return true; + } + + /* ********************************************************** */ + // id_expr mul_expr * + static boolean b_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "b_expr")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = id_expr(builder_, level_ + 1); + result_ = result_ && b_expr_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // mul_expr * + private static boolean b_expr_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "b_expr_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!mul_expr(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "b_expr_1", pos_)) break; + } + return true; + } + + /* ********************************************************** */ + // a_expr (',' a_expr) * + public static boolean expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "expr")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _COLLAPSE_, EXPR, ""); + result_ = a_expr(builder_, level_ + 1); + result_ = result_ && expr_1(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // (',' a_expr) * + private static boolean expr_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "expr_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!expr_1_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "expr_1", pos_)) break; + } + return true; + } + + // ',' a_expr + private static boolean expr_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "expr_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ","); + result_ = result_ && a_expr(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // id + public static boolean external_same_as_type2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "external_same_as_type2")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ID); + exit_section_(builder_, marker_, ID_EXPR, result_); + return result_; + } + + /* ********************************************************** */ + // number + public static boolean external_type(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "external_type")) return false; + if (!nextTokenIs(builder_, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, NUMBER); + exit_section_(builder_, marker_, MISSING_EXTERNAL_TYPE, result_); + return result_; + } + + /* ********************************************************** */ + // id + public static boolean external_type2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "external_type2")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ID); + exit_section_(builder_, marker_, ID_EXPR, result_); + return result_; + } + + /* ********************************************************** */ + // id + public static boolean external_type3(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "external_type3")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ID); + exit_section_(builder_, marker_, EXPR, result_); + return result_; + } + + /* ********************************************************** */ + // expr | external_type3 + public static boolean grammar_element(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "grammar_element")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, GRAMMAR_ELEMENT, ""); + result_ = expr(builder_, level_ + 1); + if (!result_) result_ = external_type3(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // root + static boolean grammar_root(PsiBuilder builder_, int level_) { + return root(builder_, level_ + 1); + } + + /* ********************************************************** */ + // specialRef | reference | literal | external_type | external_type2 + static boolean id_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "id_expr")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + result_ = FleetPsiGen2.specialRef(builder_, level_ + 1); + if (!result_) result_ = FleetPsiGen2.reference(builder_, level_ + 1); + if (!result_) result_ = FleetPsiGen2.literal(builder_, level_ + 1); + if (!result_) result_ = external_type(builder_, level_ + 1); + if (!result_) result_ = external_type2(builder_, level_ + 1); + return result_; + } + + /* ********************************************************** */ + // id number + public static boolean include__section__alt(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "include__section__alt")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, INCLUDE__SECTION__ALT, ""); + result_ = consumeTokens(builder_, 0, ID, NUMBER); + exit_section_(builder_, level_, marker_, result_, false, FleetPsiGen::include_section_recover_); + return result_; + } + + /* ********************************************************** */ + // id number include-section + public static boolean include_section(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "include_section")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, INCLUDE_SECTION, ""); + result_ = consumeTokens(builder_, 0, ID, NUMBER); + result_ = result_ && include_section(builder_, level_ + 1); + result_ = result_ && include__section__alt(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, FleetPsiGen::include_section_recover_); + return result_; + } + + /* ********************************************************** */ + // !() + static boolean include_section_recover_(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "include_section_recover_")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NOT_); + result_ = !include_section_recover__0(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // () + private static boolean include_section_recover__0(PsiBuilder builder_, int level_) { + return true; + } + + /* ********************************************************** */ + // <

> + + static boolean listOf(PsiBuilder builder_, int level_, Parser p) { + if (!recursion_guard_(builder_, level_, "listOf")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = p.parse(builder_, level_); + while (result_) { + int pos_ = current_position_(builder_); + if (!p.parse(builder_, level_)) break; + if (!empty_element_parsed_guard_(builder_, "listOf", pos_)) break; + } + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '*' expr + public static boolean mul_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "mul_expr")) return false; + if (!nextTokenIs(builder_, OP_MUL)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _LEFT_, MUL_EXPR, null); + result_ = consumeToken(builder_, OP_MUL); + result_ = result_ && expr(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // '+' expr + public static boolean plus_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "plus_expr")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _LEFT_, PLUS_EXPR, ""); + result_ = consumeToken(builder_, "+"); + result_ = result_ && expr(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // root_a | root_b | root_c | root_d + public static boolean root(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "root")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _COLLAPSE_, ROOT, ""); + result_ = parseGrammar(builder_, level_ + 1, FleetPsiGen::grammar_element); + if (!result_) result_ = root_b(builder_, level_ + 1); + if (!result_) result_ = root_c(builder_, level_ + 1); + if (!result_) result_ = root_d(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // <> + public static boolean root_b(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "root_b")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _COLLAPSE_, ROOT_B, ""); + result_ = parseGrammar(builder_, level_ + 1, FleetPsiGen::grammar_element); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // <> + public static boolean root_c(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "root_c")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ROOT_C, ""); + result_ = FleetPsiGen2.blockOf(builder_, level_ + 1, FleetPsiGen::grammar_element); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // <> + public static boolean root_d(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "root_d")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ROOT_D, ""); + result_ = listOf(builder_, level_ + 1, FleetPsiGen::grammar_element); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + +} +// ---- FleetPsiGen2.java ----------------- +//header.txt +package fleet; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.PsiGenUtil.*; +import static fleet.FleetPsiGen.*; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class FleetPsiGen2 { + + /* ********************************************************** */ + // <

> + + public static boolean blockOf(PsiBuilder builder_, int level_, Parser p) { + if (!recursion_guard_(builder_, level_, "blockOf")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = p.parse(builder_, level_); + while (result_) { + int pos_ = current_position_(builder_); + if (!p.parse(builder_, level_)) break; + if (!empty_element_parsed_guard_(builder_, "blockOf", pos_)) break; + } + exit_section_(builder_, marker_, BLOCK_OF, result_); + return result_; + } + + /* ********************************************************** */ + // '::' id + public static boolean cast_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "cast_expr")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _LEFT_, CAST_EXPR, ""); + result_ = consumeToken(builder_, "::"); + result_ = result_ && consumeToken(builder_, ID); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // id + public static boolean identifier(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "identifier")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ID); + exit_section_(builder_, marker_, IDENTIFIER, result_); + return result_; + } + + /* ********************************************************** */ + // '[' number ']' + public static boolean item_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "item_expr")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _LEFT_, ITEM_EXPR, ""); + result_ = consumeToken(builder_, "["); + result_ = result_ && consumeToken(builder_, NUMBER); + result_ = result_ && consumeToken(builder_, "]"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // number + public static boolean literal(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "literal")) return false; + if (!nextTokenIs(builder_, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, NUMBER); + exit_section_(builder_, marker_, LITERAL, result_); + return result_; + } + + /* ********************************************************** */ + // '.' identifier + public static boolean qref_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "qref_expr")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _LEFT_, REF_EXPR, ""); + result_ = consumeToken(builder_, "."); + result_ = result_ && identifier(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // identifier + public static boolean ref_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "ref_expr")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = identifier(builder_, level_ + 1); + exit_section_(builder_, marker_, REF_EXPR, result_); + return result_; + } + + /* ********************************************************** */ + // ref_expr qref_expr * + static boolean reference(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "reference")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = ref_expr(builder_, level_ + 1); + result_ = result_ && reference_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // qref_expr * + private static boolean reference_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "reference_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!qref_expr(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "reference_1", pos_)) break; + } + return true; + } + + /* ********************************************************** */ + // (a_expr | specialRef b_expr | some_expr_private) (cast_expr) (item_expr) * + public static boolean some_expr(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _COLLAPSE_, SOME_EXPR, ""); + result_ = some_expr_0(builder_, level_ + 1); + result_ = result_ && some_expr_1(builder_, level_ + 1); + result_ = result_ && some_expr_2(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // a_expr | specialRef b_expr | some_expr_private + private static boolean some_expr_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = a_expr(builder_, level_ + 1); + if (!result_) result_ = some_expr_0_1(builder_, level_ + 1); + if (!result_) result_ = some_expr_private(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // specialRef b_expr + private static boolean some_expr_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr_0_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = specialRef(builder_, level_ + 1); + result_ = result_ && b_expr(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // (cast_expr) + private static boolean some_expr_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = cast_expr(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // (item_expr) * + private static boolean some_expr_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!some_expr_2_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "some_expr_2", pos_)) break; + } + return true; + } + + // (item_expr) + private static boolean some_expr_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr_2_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = item_expr(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // specialRef b_expr + static boolean some_expr_private(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "some_expr_private")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = specialRef(builder_, level_ + 1); + result_ = result_ && b_expr(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // identifier OF reference + public static boolean specialRef(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "specialRef")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = identifier(builder_, level_ + 1); + result_ = result_ && consumeToken(builder_, OF); + result_ = result_ && reference(builder_, level_ + 1); + exit_section_(builder_, marker_, SPECIAL_REF, result_); + return result_; + } + +} +// ---- FleetPsiGenFixes.java ----------------- +//header.txt +package fleet; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.PsiGenUtil.*; +import static fleet.FleetPsiGen.*; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class FleetPsiGenFixes { + + /* ********************************************************** */ + // ',' identifier + public static boolean LeftShadow(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "LeftShadow")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _LEFT_, LEFT_SHADOW, ""); + result_ = consumeToken(builder_, ","); + result_ = result_ && FleetPsiGen2.identifier(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // identifier LeftShadow * + public static boolean LeftShadowTest(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "LeftShadowTest")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = FleetPsiGen2.identifier(builder_, level_ + 1); + result_ = result_ && LeftShadowTest_1(builder_, level_ + 1); + exit_section_(builder_, marker_, LEFT_SHADOW_TEST, result_); + return result_; + } + + // LeftShadow * + private static boolean LeftShadowTest_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "LeftShadowTest_1")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!LeftShadow(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "LeftShadowTest_1", pos_)) break; + } + return true; + } + + /* ********************************************************** */ + // id | number + public static boolean a_statement(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "a_statement")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, A_STATEMENT, ""); + result_ = consumeToken(builder_, ID); + if (!result_) result_ = consumeToken(builder_, NUMBER); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // id | number + public static boolean b_statement(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "b_statement")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, B_STATEMENT, ""); + result_ = consumeToken(builder_, ID); + if (!result_) result_ = consumeToken(builder_, NUMBER); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // id | number + public static boolean c_statement(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "c_statement")) return false; + if (!nextTokenIs(builder_, "", ID, NUMBER)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, C_STATEMENT, ""); + result_ = consumeToken(builder_, ID); + if (!result_) result_ = consumeToken(builder_, NUMBER); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // literal id '%' | '%' id literal + public static boolean choice_joined(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "choice_joined")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, CHOICE_JOINED, ""); + result_ = choice_joined_0(builder_, level_ + 1); + if (!result_) result_ = choice_joined_1(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // literal id '%' + private static boolean choice_joined_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "choice_joined_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = FleetPsiGen2.literal(builder_, level_ + 1); + result_ = result_ && consumeToken(builder_, ID); + result_ = result_ && consumeToken(builder_, "%"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // '%' id literal + private static boolean choice_joined_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "choice_joined_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "%"); + result_ = result_ && consumeToken(builder_, ID); + result_ = result_ && FleetPsiGen2.literal(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // <> + static boolean fixMetaRule(PsiBuilder builder_, int level_) { + return FleetPsiGen2.blockOf(builder_, level_ + 1, FleetPsiGen2::identifier); + } + + /* ********************************************************** */ + // identifier + public static boolean publicMethodToCall(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "publicMethodToCall")) return false; + if (!nextTokenIs(builder_, ID)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = FleetPsiGen2.identifier(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // &<> (a_statement | b_statement) | !<> (c_statement) + public static boolean statement(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _COLLAPSE_, STATEMENT, ""); + result_ = statement_0(builder_, level_ + 1); + if (!result_) result_ = statement_1(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // &<> (a_statement | b_statement) + private static boolean statement_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = statement_0_0(builder_, level_ + 1); + result_ = result_ && statement_0_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // &<> + private static boolean statement_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement_0_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _AND_); + result_ = external(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // a_statement | b_statement + private static boolean statement_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement_0_1")) return false; + boolean result_; + result_ = a_statement(builder_, level_ + 1); + if (!result_) result_ = b_statement(builder_, level_ + 1); + return result_; + } + + // !<> (c_statement) + private static boolean statement_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = statement_1_0(builder_, level_ + 1); + result_ = result_ && statement_1_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // !<> + private static boolean statement_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NOT_); + result_ = !external(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // (c_statement) + private static boolean statement_1_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement_1_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = c_statement(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + +} \ No newline at end of file diff --git a/testData/fleet/IFIleTypeGeneration.bnf b/testData/fleet/IFIleTypeGeneration.bnf new file mode 100644 index 00000000..130f9ba4 --- /dev/null +++ b/testData/fleet/IFIleTypeGeneration.bnf @@ -0,0 +1,9 @@ +{ + extraRoot(".*")=true +} + +grammar ::= list | map +list ::= '(' element (',' element) * ')' +map ::= '(' entry (',' entry) * ')' +entry ::= 'name' '->' element +element ::= 'id' \ No newline at end of file diff --git a/testData/fleet/IFileTypeGeneration.PSI.expected.java b/testData/fleet/IFileTypeGeneration.PSI.expected.java new file mode 100644 index 00000000..90e5ad8b --- /dev/null +++ b/testData/fleet/IFileTypeGeneration.PSI.expected.java @@ -0,0 +1,37 @@ +// ---- GeneratedTypes.java ----------------- +// This is a generated file. Not intended for manual editing. +package fleet.generated; + +import fleet.com.intellij.psi.tree.IElementType; + +public interface GeneratedTypes { + + IElementType ELEMENT = new IElementType("ELEMENT", null); + IElementType ENTRY = new IElementType("ENTRY", null); + IElementType LIST = new IElementType("LIST", null); + IElementType MAP = new IElementType("MAP", null); + +} +// ---- MyFileType.java ----------------- +// This is a generated file. Not intended for manual editing. +package fleet.some.filetype.psi; + +import fleet.some.language.MyLanguage; +import fleet.com.intellij.psi.tree.IFileElementType; +import fleet.com.intellij.lang.PsiBuilder; +import org.jetbrains.annotations.NotNull; +import fleet.generated.GeneratedParser; + +public class MyFileType extends IFileElementType { + + public static final MyFileType INSTANCE = new MyFileType(); + + public MyFileType() { + super("TEST", MyLanguage.INSTANCE); + } + + @Override + public void parse(@NotNull PsiBuilder builder) { + new GeneratedParser().parseLight(this, builder); + } +} \ No newline at end of file diff --git a/testData/fleet/IFileTypeGeneration.expected.java b/testData/fleet/IFileTypeGeneration.expected.java new file mode 100644 index 00000000..4d34c26d --- /dev/null +++ b/testData/fleet/IFileTypeGeneration.expected.java @@ -0,0 +1,161 @@ +// ---- GeneratedParser.java ----------------- +// This is a generated file. Not intended for manual editing. +package fleet.generated; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.com.intellij.lang.parser.GeneratedParserUtilBase.*; +import fleet.com.intellij.psi.tree.IElementType; +import fleet.com.intellij.lang.ASTNode; +import fleet.com.intellij.psi.tree.TokenSet; +import fleet.com.intellij.lang.PsiParser; +import fleet.com.intellij.lang.LightPsiParser; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class GeneratedParser implements PsiParser, LightPsiParser { + + public ASTNode parse(IElementType root_, PsiBuilder builder_) { + parseLight(root_, builder_); + return builder_.getTreeBuilt(); + } + + public void parseLight(IElementType root_, PsiBuilder builder_) { + boolean result_; + builder_ = adapt_builder_(root_, builder_, this, null); + Marker marker_ = enter_section_(builder_, 0, _COLLAPSE_, null); + result_ = parse_root_(root_, builder_); + exit_section_(builder_, 0, marker_, root_, result_, true, TRUE_CONDITION); + } + + protected boolean parse_root_(IElementType root_, PsiBuilder builder_) { + return parse_root_(root_, builder_, 0); + } + + static boolean parse_root_(IElementType root_, PsiBuilder builder_, int level_) { + boolean result_; + if (root_ == ELEMENT) { + result_ = element(builder_, level_ + 1); + } + else if (root_ == ENTRY) { + result_ = entry(builder_, level_ + 1); + } + else if (root_ == LIST) { + result_ = list(builder_, level_ + 1); + } + else if (root_ == MAP) { + result_ = map(builder_, level_ + 1); + } + else { + result_ = grammar(builder_, level_ + 1); + } + return result_; + } + + /* ********************************************************** */ + // 'id' + public static boolean element(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "element")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ELEMENT, ""); + result_ = consumeToken(builder_, "id"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // 'name' '->' element + public static boolean entry(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "entry")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ENTRY, ""); + result_ = consumeToken(builder_, "name"); + result_ = result_ && consumeToken(builder_, "->"); + result_ = result_ && element(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // list | map + static boolean grammar(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "grammar")) return false; + boolean result_; + result_ = list(builder_, level_ + 1); + if (!result_) result_ = map(builder_, level_ + 1); + return result_; + } + + /* ********************************************************** */ + // '(' element (',' element) * ')' + public static boolean list(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "list")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, LIST, ""); + result_ = consumeToken(builder_, "("); + result_ = result_ && element(builder_, level_ + 1); + result_ = result_ && list_2(builder_, level_ + 1); + result_ = result_ && consumeToken(builder_, ")"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // (',' element) * + private static boolean list_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "list_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!list_2_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "list_2", pos_)) break; + } + return true; + } + + // ',' element + private static boolean list_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "list_2_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ","); + result_ = result_ && element(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '(' entry (',' entry) * ')' + public static boolean map(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "map")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, MAP, ""); + result_ = consumeToken(builder_, "("); + result_ = result_ && entry(builder_, level_ + 1); + result_ = result_ && map_2(builder_, level_ + 1); + result_ = result_ && consumeToken(builder_, ")"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + // (',' entry) * + private static boolean map_2(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "map_2")) return false; + while (true) { + int pos_ = current_position_(builder_); + if (!map_2_0(builder_, level_ + 1)) break; + if (!empty_element_parsed_guard_(builder_, "map_2", pos_)) break; + } + return true; + } + + // ',' entry + private static boolean map_2_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "map_2_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, ","); + result_ = result_ && entry(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + +} \ No newline at end of file diff --git a/tests/org/intellij/grammar/BnfGeneratorAbstractTest.java b/tests/org/intellij/grammar/BnfGeneratorAbstractTest.java new file mode 100644 index 00000000..bffe7b8a --- /dev/null +++ b/tests/org/intellij/grammar/BnfGeneratorAbstractTest.java @@ -0,0 +1,93 @@ +package org.intellij.grammar; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.psi.PsiFile; +import org.intellij.grammar.generator.GeneratorBase; +import org.intellij.grammar.generator.ParserGenerator; +import org.intellij.grammar.psi.BnfFile; +import org.jetbrains.annotations.NonNls; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author gregsh + */ +public abstract class BnfGeneratorAbstractTest extends BnfGeneratorTestCase { + + public BnfGeneratorAbstractTest(String s) { + super(s); + } + + protected List newTestGenerator() { + var list = new ArrayList(); + var bnfFile = (BnfFile)myFile; + list.add((new ParserGenerator(bnfFile, "", myFullDataPath, "") { + @Override + protected PrintWriter openOutputInner(String className, File file) throws IOException { + String grammarName = FileUtil.getNameWithoutExtension(this.myFile.getName()); + String fileName = FileUtil.getNameWithoutExtension(file); + String name = grammarName + (fileName.startsWith(grammarName) || fileName.endsWith("Parser") ? "" : ".PSI") + ".java"; + File targetFile = new File(FileUtilRt.getTempDirectory(), name); + targetFile.getParentFile().mkdirs(); + FileOutputStream outputStream = new FileOutputStream(targetFile, true); + PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, this.myFile.getVirtualFile().getCharset())); + out.println("// ---- " + file.getName() + " -----------------"); + return out; + } + })); + return list; + } + + @Override + protected String loadFile(@NonNls String name) throws IOException { + if (name.equals("SelfBnf.bnf")) return super.loadFile("../../grammars/Grammar.bnf"); + if (name.equals("SelfFlex.bnf")) return super.loadFile("../../grammars/JFlex.bnf"); + return super.loadFile(name); + } + + public void doGenTest(boolean generatePsi) throws Exception { + String name = getTestName(false); + String text = loadFile(name + "." + myFileExt); + myFile = createBnfFile(generatePsi, name, text); + List filesToCheck = new ArrayList<>(); + filesToCheck.add(new File(FileUtilRt.getTempDirectory(), name + ".java")); + if (generatePsi) { + filesToCheck.add(new File(FileUtilRt.getTempDirectory(), name + ".PSI.java")); + } + for (File file : filesToCheck) { + if (file.exists()) { + assertTrue(file.delete()); + } + } + + for (GeneratorBase generator : newTestGenerator()) { + if (generator instanceof ParserGenerator parserGenerator) { + if (generatePsi) { + parserGenerator.generate(); + } + else { + parserGenerator.generateParser(); + } + } + else { + generator.generate(); + } + } + + + for (File file : filesToCheck) { + assertTrue("Generated file not found: " + file, file.exists()); + String expectedName = FileUtil.getNameWithoutExtension(file) + ".expected.java"; + String result = FileUtil.loadFile(file, CharsetToolkit.UTF8, true); + doCheckResult(myFullDataPath, expectedName, result); + } + } + + protected PsiFile createBnfFile(boolean generatePsi, String name, String text) { + return createPsiFile(name, text.replaceAll("generatePsi=[^\n]*", "generatePsi=" + generatePsi)); + } +} diff --git a/tests/org/intellij/grammar/BnfGeneratorTest.java b/tests/org/intellij/grammar/BnfGeneratorTest.java index 487ea9d1..5a80110a 100644 --- a/tests/org/intellij/grammar/BnfGeneratorTest.java +++ b/tests/org/intellij/grammar/BnfGeneratorTest.java @@ -1,22 +1,12 @@ +/* + * 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; import com.intellij.idea.Bombed; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.CharsetToolkit; -import org.intellij.grammar.generator.ParserGenerator; -import org.intellij.grammar.psi.impl.BnfFileImpl; -import org.jetbrains.annotations.NonNls; - -import java.io.*; -import java.util.ArrayList; -import java.util.List; - -/** - * @author gregsh - */ -public class BnfGeneratorTest extends BnfGeneratorTestCase { +public class BnfGeneratorTest extends BnfGeneratorAbstractTest { public BnfGeneratorTest() { super("generator"); } @@ -43,63 +33,13 @@ public BnfGeneratorTest() { public void testGenOptions() throws Exception { doGenTest(true); } @Bombed(year = 2030, user = "author", month = 1, day = 1, description = "not implemented") - public void _testUpperRules() throws Exception { doGenTest(true); } + public void testUpperRules() throws Exception { doGenTest(true); } public void testFixes() throws Exception { doGenTest(true); } public void testEmpty() throws Exception { myFile = createPsiFile("empty.bnf", "{ }"); - newTestGenerator().generate(); - } - - private ParserGenerator newTestGenerator() { - return new ParserGenerator((BnfFileImpl)myFile, "", myFullDataPath, "") { - - @Override - protected PrintWriter openOutputInner(String className, File file) throws IOException { - String grammarName = FileUtil.getNameWithoutExtension(myFile.getName()); - String fileName = FileUtil.getNameWithoutExtension(file); - String name = grammarName + (fileName.startsWith(grammarName) || fileName.endsWith("Parser") ? "" : ".PSI") + ".java"; - File targetFile = new File(FileUtilRt.getTempDirectory(), name); - targetFile.getParentFile().mkdirs(); - FileOutputStream outputStream = new FileOutputStream(targetFile, true); - PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, myFile.getVirtualFile().getCharset())); - out.println("// ---- " + file.getName() + " -----------------"); - return out; - } - }; - } - - @Override - protected String loadFile(@NonNls String name) throws IOException { - if (name.equals("SelfBnf.bnf")) return super.loadFile("../../grammars/Grammar.bnf"); - if (name.equals("SelfFlex.bnf")) return super.loadFile("../../grammars/JFlex.bnf"); - return super.loadFile(name); - } - - public void doGenTest(boolean generatePsi) throws Exception { - String name = getTestName(false); - String text = loadFile(name + "." + myFileExt); - myFile = createPsiFile(name, text.replaceAll("generatePsi=[^\n]*", "generatePsi=" + generatePsi)); - List filesToCheck = new ArrayList<>(); - filesToCheck.add(new File(FileUtilRt.getTempDirectory(), name + ".java")); - if (generatePsi) { - filesToCheck.add(new File(FileUtilRt.getTempDirectory(), name + ".PSI.java")); - } - for (File file : filesToCheck) { - if (file.exists()) { - assertTrue(file.delete()); - } - } - - ParserGenerator parserGenerator = newTestGenerator(); - if (generatePsi) parserGenerator.generate(); - else parserGenerator.generateParser(); - - for (File file : filesToCheck) { - assertTrue("Generated file not found: " + file, file.exists()); - String expectedName = FileUtil.getNameWithoutExtension(file) + ".expected.java"; - String result = FileUtil.loadFile(file, CharsetToolkit.UTF8, true); - doCheckResult(myFullDataPath, expectedName, result); + for (var generator : newTestGenerator()) { + generator.generate(); } } } diff --git a/tests/org/intellij/grammar/BnfTestSuite.java b/tests/org/intellij/grammar/BnfTestSuite.java index 6ec3d1a0..56de2cf4 100644 --- a/tests/org/intellij/grammar/BnfTestSuite.java +++ b/tests/org/intellij/grammar/BnfTestSuite.java @@ -28,6 +28,7 @@ public static Test suite() { testSuite.addTestSuite(JFlexParserTest.class); testSuite.addTestSuite(BnfParserTest.class); testSuite.addTestSuite(BnfGeneratorTest.class); + testSuite.addTestSuite(FleetBnfGeneratorTest.class); testSuite.addTestSuite(ExpressionParserTest.class); testSuite.addTestSuite(BnfLivePreviewParserTest.class); return testSuite; diff --git a/tests/org/intellij/grammar/FleetBnfGeneratorTest.java b/tests/org/intellij/grammar/FleetBnfGeneratorTest.java new file mode 100644 index 00000000..63f3eb03 --- /dev/null +++ b/tests/org/intellij/grammar/FleetBnfGeneratorTest.java @@ -0,0 +1,68 @@ +/* + * 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; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.psi.PsiFile; +import org.intellij.grammar.generator.GeneratorBase; +import org.intellij.grammar.fleet.FleetBnfFileWrapper; +import org.intellij.grammar.fleet.FleetFileTypeGenerator; +import org.intellij.grammar.psi.BnfFile; + +import java.io.*; +import java.util.List; + +public class FleetBnfGeneratorTest extends BnfGeneratorAbstractTest { + + private FileGeneratorParams myFileGeneratorParams = null; + private record FileGeneratorParams(boolean doGenerate, String className, String debugName, String languageClass) {} + + public FleetBnfGeneratorTest() { + super("fleet"); + } + + @Override + protected List newTestGenerator() { + var listOfGens = super.newTestGenerator(); + if (myFileGeneratorParams != null) { + var fileTypeGenerator = new FleetFileTypeGenerator((BnfFile)myFile, + "", myFullDataPath, "", + myFileGeneratorParams.className, + myFileGeneratorParams.debugName, + myFileGeneratorParams.languageClass) { + @Override + protected PrintWriter openOutputInner(String className, File file) throws IOException { + String grammarName = FileUtil.getNameWithoutExtension(this.myFile.getName()); + String name = grammarName + ".PSI.java"; + File targetFile = new File(FileUtilRt.getTempDirectory(), name); + targetFile.getParentFile().mkdirs(); + FileOutputStream outputStream = new FileOutputStream(targetFile, true); + PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, this.myFile.getVirtualFile().getCharset())); + out.println("// ---- " + file.getName() + " -----------------"); + return out; + } + }; + listOfGens.add(fileTypeGenerator); + } + return listOfGens; + } + + @Override + protected PsiFile createBnfFile(boolean generatePsi, String name, String text) { + return FleetBnfFileWrapper.wrapBnfFile((BnfFile)createPsiFile(name, text.replaceAll("generatePsi=[^\n]*", "generatePsi=" + generatePsi))); + } + + public void doGenTest(boolean generatePsi, String fileTypeClass, String debugName, String languageClass) throws Exception { + myFileGeneratorParams = new FileGeneratorParams(true, fileTypeClass, debugName, languageClass); + super.doGenTest(generatePsi); + myFileGeneratorParams = null; + } + + public void testIFileTypeGeneration() throws Exception { doGenTest(true, "some.filetype.psi.MyFileType", "TEST", "some.language.MyLanguage");} + public void testFleetPsiGen() throws Exception { doGenTest(true);} + public void testFleetExprParser() throws Exception { doGenTest(true);} + public void testFleetExternalRules() throws Exception { doGenTest(false);} +}

> + static boolean main_class_meta(PsiBuilder builder_, int level_, Parser p) { + return p.parse(builder_, level_); + } + + /* ********************************************************** */ + // <>>> + static boolean meta_mixed(PsiBuilder builder_, int level_, Parser param) { + return listOf(builder_, level_ + 1, "1+2", 1+2, param); + } + + /* ********************************************************** */ + // <>>> + static boolean meta_mixed_list(PsiBuilder builder_, int level_) { + return meta_mixed(builder_, level_ + 1, meta_mixed_list_0_0_parser_); + } + + /* ********************************************************** */ + // <>)>> + static boolean meta_mixed_list_paren(PsiBuilder builder_, int level_) { + return meta_mixed(builder_, level_ + 1, FleetExternalRules::meta_mixed_list_paren_0_0); + } + + // <> + private static boolean meta_mixed_list_paren_0_0(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, FleetExternalRules::one); + } + + /* ********************************************************** */ + // <> + static boolean meta_mixed_simple(PsiBuilder builder_, int level_) { + return meta_mixed(builder_, level_ + 1, FleetExternalRules::statement); + } + + /* ********************************************************** */ + // <>>>>>>>>>>> + public static boolean meta_multi_level(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "meta_multi_level")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = comma_list(builder_, level_ + 1, comma_list_$(comma_list_$(comma_list_$(comma_list_$(param))))); + exit_section_(builder_, marker_, META_MULTI_LEVEL, result_); + return result_; + } + + /* ********************************************************** */ + // <>>>>> + static boolean meta_multi_level_no_closure(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, meta_multi_level_no_closure_0_0_parser_); + } + + /* ********************************************************** */ + // <> <>>>>>>>>>>> + public static boolean meta_multi_level_pinned(PsiBuilder builder_, int level_, Parser head, Parser param) { + if (!recursion_guard_(builder_, level_, "meta_multi_level_pinned")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = comma_list(builder_, level_ + 1, comma_list_pinned_$(head, comma_list_$(comma_list_$(comma_list_$(param))))); + exit_section_(builder_, marker_, META_MULTI_LEVEL_PINNED, result_); + return result_; + } + + /* ********************************************************** */ + // <> (<>>>>>>>)>>>> + public static boolean meta_multi_level_pinned_paren(PsiBuilder builder_, int level_, Parser head, Parser param) { + if (!recursion_guard_(builder_, level_, "meta_multi_level_pinned_paren")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = comma_list(builder_, level_ + 1, comma_list_pinned_$(head, meta_multi_level_pinned_paren_0_0_1_$(param))); + exit_section_(builder_, marker_, META_MULTI_LEVEL_PINNED_PAREN, result_); + return result_; + } + + private static Parser meta_multi_level_pinned_paren_0_0_1_$(Parser param) { + return (builder_, level_) -> meta_multi_level_pinned_paren_0_0_1(builder_, level_ + 1, param); + } + + // <>>>>>>> + private static boolean meta_multi_level_pinned_paren_0_0_1(PsiBuilder builder_, int level_, Parser param) { + return comma_list(builder_, level_ + 1, comma_list_$(comma_list_$(param))); + } + + /* ********************************************************** */ + // <> + static boolean meta_seq(PsiBuilder builder_, int level_) { + return comma_list_pinned(builder_, level_ + 1, FleetExternalRules::one, FleetExternalRules::meta_seq_0_1); + } + + // one | two + private static boolean meta_seq_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "meta_seq_0_1")) return false; + boolean result_; + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + return result_; + } + + /* ********************************************************** */ + // <> + static boolean meta_seq_of_lists(PsiBuilder builder_, int level_) { + return list_of_lists(builder_, level_ + 1, FleetExternalRules::one, FleetExternalRules::meta_seq_of_lists_0_1); + } + + // one | two + private static boolean meta_seq_of_lists_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "meta_seq_of_lists_0_1")) return false; + boolean result_; + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + return result_; + } + + /* ********************************************************** */ + // (<>)? + static boolean meta_seq_of_lists_opt(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "meta_seq_of_lists_opt")) return false; + meta_seq_of_lists_opt_0(builder_, level_ + 1); + return true; + } + + // <> + private static boolean meta_seq_of_lists_opt_0(PsiBuilder builder_, int level_) { + return list_of_lists(builder_, level_ + 1, FleetExternalRules::one, FleetExternalRules::meta_seq_of_lists_opt_0_0_1); + } + + // one | two + private static boolean meta_seq_of_lists_opt_0_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "meta_seq_of_lists_opt_0_0_1")) return false; + boolean result_; + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + return result_; + } + + /* ********************************************************** */ + // <> + static boolean meta_simple(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, FleetExternalRules::one); + } + + /* ********************************************************** */ + // <> | some)>> + public static boolean meta_with_in_place(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "meta_with_in_place")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = comma_list(builder_, level_ + 1, meta_with_in_place_0_0_$(param)); + exit_section_(builder_, marker_, META_WITH_IN_PLACE, result_); + return result_; + } + + private static Parser meta_with_in_place_0_0_$(Parser param) { + return (builder_, level_) -> meta_with_in_place_0_0(builder_, level_ + 1, param); + } + + // <> | some + private static boolean meta_with_in_place_0_0(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "meta_with_in_place_0_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = param.parse(builder_, level_); + if (!result_) result_ = consumeToken(builder_, SOME); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // <> + static boolean multi_level(PsiBuilder builder_, int level_) { + return meta_multi_level(builder_, level_ + 1, FleetExternalRules::one); + } + + /* ********************************************************** */ + // <> <> <>>>>> + static boolean nested_meta(PsiBuilder builder_, int level_, Parser nested1, Parser nested2, Parser nested3) { + return two_params_meta(builder_, level_ + 1, nested1, two_params_meta_$(nested2, nested3)); + } + + /* ********************************************************** */ + // <>>>) perc_re>> + static boolean nested_mixed(PsiBuilder builder_, int level_, Parser c) { + return two_params_meta(builder_, level_ + 1, nested_mixed_0_0_$(c), PERC_RE_parser_); + } + + private static Parser nested_mixed_0_0_$(Parser c) { + return (builder_, level_) -> nested_mixed_0_0(builder_, level_ + 1, c); + } + + // <>>> + private static boolean nested_mixed_0_0(PsiBuilder builder_, int level_, Parser c) { + return two_params_meta(builder_, level_ + 1, perc_parser_, c); + } + + /* ********************************************************** */ + // 'one' + public static boolean one(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "one")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, ONE, ""); + result_ = consumeToken(builder_, "one"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_choice(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_choice")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, FleetExternalRules::param_choice_1_0); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // one | two | 10 | some + private static boolean param_choice_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_choice_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + if (!result_) result_ = consumeToken(builder_, "10"); + if (!result_) result_ = consumeToken(builder_, SOME); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_choice_alt(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_choice_alt")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, FleetExternalRules::param_choice_alt_1_0); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // one | two | 10 | some + private static boolean param_choice_alt_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_choice_alt_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + if (!result_) result_ = consumeToken(builder_, "10"); + if (!result_) result_ = consumeToken(builder_, SOME); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_opt(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_opt")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, FleetExternalRules::param_opt_1_0); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // [one | two | 10 | some] + private static boolean param_opt_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_opt_1_0")) return false; + param_opt_1_0_0(builder_, level_ + 1); + return true; + } + + // one | two | 10 | some + private static boolean param_opt_1_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_opt_1_0_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + if (!result_) result_ = consumeToken(builder_, "10"); + if (!result_) result_ = consumeToken(builder_, SOME); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_seq(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, "1+1", 1+1, FleetExternalRules::one, FleetExternalRules::two, 10, SOME_parser_); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_seq_alt(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, FleetExternalRules::param_seq_alt_1_0, FleetExternalRules::param_seq_alt_1_1); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // one | two + private static boolean param_seq_alt_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt_1_0")) return false; + boolean result_; + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + return result_; + } + + // [10 | some] + private static boolean param_seq_alt_1_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt_1_1")) return false; + param_seq_alt_1_1_0(builder_, level_ + 1); + return true; + } + + // 10 | some + private static boolean param_seq_alt_1_1_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt_1_1_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "10"); + if (!result_) result_ = consumeToken(builder_, SOME); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_seq_alt_ext(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt_ext")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, FleetExternalRules::one, FleetExternalRules::two); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + // '{' <> '}' + static boolean param_seq_alt_params_ext(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt_params_ext")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, "{"); + result_ = result_ && uniqueListOf(builder_, level_ + 1, FleetExternalRules::one, "1+1", FleetExternalRules::param_seq_alt_params_ext_1_1, 1+1); + result_ = result_ && consumeToken(builder_, "}"); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // !two + private static boolean param_seq_alt_params_ext_1_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "param_seq_alt_params_ext_1_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NOT_); + result_ = !two(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // <> + static boolean perc_list(PsiBuilder builder_, int level_) { + return listOf(builder_, level_ + 1, perc_parser_); + } + + /* ********************************************************** */ + // <> + static boolean perc_re_list1(PsiBuilder builder_, int level_) { + return listOf(builder_, level_ + 1, PERC_RE_parser_); + } + + /* ********************************************************** */ + // <> + static boolean perc_re_list2(PsiBuilder builder_, int level_) { + return listOf(builder_, level_ + 1, perc_re_list2_0_0_parser_); + } + + /* ********************************************************** */ + // <> + public static boolean public_paren_list(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "public_paren_list")) return false; + if (!nextTokenIs(builder_, PAREN1)) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = comma_paren_list(builder_, level_ + 1, FleetExternalRules::public_paren_list_0_0); + exit_section_(builder_, marker_, PUBLIC_PAREN_LIST, result_); + return result_; + } + + // ref | '(' one ')' + private static boolean public_paren_list_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "public_paren_list_0_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = parseRef(builder_, level_ + 1); + if (!result_) result_ = public_paren_list_0_0_1(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + // '(' one ')' + private static boolean public_paren_list_0_0_1(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "public_paren_list_0_0_1")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = consumeToken(builder_, PAREN1); + result_ = result_ && one(builder_, level_ + 1); + result_ = result_ && consumeToken(builder_, PAREN2); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + public static boolean public_paren_list2(PsiBuilder builder_, int level_) { + Marker marker_ = enter_section_(builder_); + exit_section_(builder_, marker_, PUBLIC_PAREN_LIST, true); + return true; + } + + /* ********************************************************** */ + // <> + static boolean recoverable_item(PsiBuilder builder_, int level_, Parser param) { + if (!recursion_guard_(builder_, level_, "recoverable_item")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = param.parse(builder_, level_); + exit_section_(builder_, level_, marker_, result_, false, FleetExternalRules::item_recover); + return result_; + } + + /* ********************************************************** */ + // <> + static boolean recoverable_item2(PsiBuilder builder_, int level_, Parser param, Parser recover_arg) { + if (!recursion_guard_(builder_, level_, "recoverable_item2")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = param.parse(builder_, level_); + exit_section_(builder_, level_, marker_, result_, false, recover_arg); + return result_; + } + + /* ********************************************************** */ + // <> <> + static boolean recoverable_item3(PsiBuilder builder_, int level_, Parser recover_arg, Parser param) { + if (!recursion_guard_(builder_, level_, "recoverable_item3")) return false; + boolean result_, pinned_; + Marker marker_ = enter_section_(builder_, level_, _NONE_); + result_ = recover_arg.parse(builder_, level_); + pinned_ = result_; // pin = 1 + result_ = result_ && param.parse(builder_, level_); + exit_section_(builder_, level_, marker_, result_, pinned_, recover_arg); + return result_ || pinned_; + } + + /* ********************************************************** */ + // <> + static boolean root(PsiBuilder builder_, int level_) { + return listOf(builder_, level_ + 1, FleetExternalRules::statement); + } + + /* ********************************************************** */ + // <>>> + static boolean second_class_meta_usage_from_main(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, second_class_meta_usage_from_main_0_0_parser_); + } + + /* ********************************************************** */ + // one | two + public static boolean statement(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "statement")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, STATEMENT, ""); + result_ = one(builder_, level_ + 1); + if (!result_) result_ = two(builder_, level_ + 1); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + // 'two' + public static boolean two(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "two")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_, level_, _NONE_, TWO, ""); + result_ = consumeToken(builder_, "two"); + exit_section_(builder_, level_, marker_, result_, false, null); + return result_; + } + + /* ********************************************************** */ + static Parser two_params_meta_$(Parser a, Parser b) { + return (builder_, level_) -> two_params_meta(builder_, level_ + 1, a, b); + } + + // <> <> + public static boolean two_params_meta(PsiBuilder builder_, int level_, Parser a, Parser b) { + if (!recursion_guard_(builder_, level_, "two_params_meta")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = a.parse(builder_, level_); + result_ = result_ && b.parse(builder_, level_); + exit_section_(builder_, marker_, TWO_PARAMS_META, result_); + return result_; + } + + static final Parser PERC_RE_parser_ = (builder_, level_) -> consumeToken(builder_, PERC_RE); + static final Parser SOME_parser_ = (builder_, level_) -> consumeToken(builder_, SOME); + static final Parser perc_parser_ = (builder_, level_) -> consumeToken(builder_, PERC); + static final Parser perc_re_list2_0_0_parser_ = PERC_RE_parser_; + + private static final Parser meta_mixed_list_0_0_parser_ = comma_list_$(FleetExternalRules::one); + private static final Parser meta_multi_level_no_closure_0_0_0_parser_ = comma_list_$(SOME_parser_); + private static final Parser meta_multi_level_no_closure_0_0_parser_ = comma_list_$(meta_multi_level_no_closure_0_0_0_parser_); + private static final Parser second_class_meta_usage_from_main_0_0_parser_ = FleetExternalRules2.second_class_meta_$(SOME_parser_); +} +// ---- FleetExternalRules2.java ----------------- +// This is a generated file. Not intended for manual editing. +package fleet; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.org.intellij.grammar.test.ParserUtil.*; +import static fleet.FleetExternalRules.*; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class FleetExternalRules2 { + + /* ********************************************************** */ + public static boolean extra_root(PsiBuilder builder_, int level_) { + Marker marker_ = enter_section_(builder_); + exit_section_(builder_, marker_, EXTRA_ROOT, true); + return true; + } + + /* ********************************************************** */ + // <>>> + static boolean main_class_meta_usage_from_second(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, main_class_meta_usage_from_second_0_0_parser_); + } + + /* ********************************************************** */ + // <> + static boolean one_list(PsiBuilder builder_, int level_) { + return listOf(builder_, level_ + 1, FleetExternalRules::one); + } + + /* ********************************************************** */ + // <> + static boolean one_list_par(PsiBuilder builder_, int level_) { + return listOf(builder_, level_ + 1, FleetExternalRules2::one_list_par_0_0); + } + + // (one) + private static boolean one_list_par_0_0(PsiBuilder builder_, int level_) { + if (!recursion_guard_(builder_, level_, "one_list_par_0_0")) return false; + boolean result_; + Marker marker_ = enter_section_(builder_); + result_ = one(builder_, level_ + 1); + exit_section_(builder_, marker_, null, result_); + return result_; + } + + /* ********************************************************** */ + static Parser second_class_meta_$(Parser bmp) { + return (builder_, level_) -> second_class_meta(builder_, level_ + 1, bmp); + } + + // <> + static boolean second_class_meta(PsiBuilder builder_, int level_, Parser bmp) { + return bmp.parse(builder_, level_); + } + + /* ********************************************************** */ + // <>>> + static boolean third_class_meta_usage_from_second(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, third_class_meta_usage_from_second_0_0_parser_); + } + + private static final Parser main_class_meta_usage_from_second_0_0_parser_ = main_class_meta_$(FleetExternalRules.SOME_parser_); + private static final Parser third_class_meta_usage_from_second_0_0_parser_ = FleetExternalRules3.third_class_meta_$(FleetExternalRules.SOME_parser_); +} +// ---- FleetExternalRules3.java ----------------- +// This is a generated file. Not intended for manual editing. +package fleet; + +import fleet.com.intellij.lang.PsiBuilder; +import fleet.com.intellij.lang.PsiBuilder.Marker; +import static fleet.generated.GeneratedTypes.*; +import static fleet.org.intellij.grammar.test.ParserUtil.*; +import static fleet.FleetExternalRules.*; + +@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"}) +public class FleetExternalRules3 { + + /* ********************************************************** */ + // <>>> + static boolean second_class_meta_usage_from_third(PsiBuilder builder_, int level_) { + return comma_list(builder_, level_ + 1, second_class_meta_usage_from_third_0_0_parser_); + } + + /* ********************************************************** */ + static Parser third_class_meta_$(Parser fmp) { + return (builder_, level_) -> third_class_meta(builder_, level_ + 1, fmp); + } + + // <> + static boolean third_class_meta(PsiBuilder builder_, int level_, Parser fmp) { + return fmp.parse(builder_, level_); + } + + private static final Parser second_class_meta_usage_from_third_0_0_parser_ = FleetExternalRules2.second_class_meta_$(FleetExternalRules.SOME_parser_); +} \ No newline at end of file diff --git a/testData/fleet/FleetPsiGen.PSI.expected.java b/testData/fleet/FleetPsiGen.PSI.expected.java new file mode 100644 index 00000000..9c644839 --- /dev/null +++ b/testData/fleet/FleetPsiGen.PSI.expected.java @@ -0,0 +1,45 @@ +// ---- GeneratedTypes.java ----------------- +//header.txt +package fleet.generated; + +import fleet.com.intellij.psi.tree.IElementType; +import fleet.sample.MyRootType; + +public interface GeneratedTypes { + + IElementType A_STATEMENT = new IElementType("A_STATEMENT", null); + IElementType BLOCK_OF = new IElementType("BLOCK_OF", null); + IElementType B_STATEMENT = new IElementType("B_STATEMENT", null); + IElementType CAST_EXPR = new IElementType("CAST_EXPR", null); + IElementType CHOICE_JOINED = new IElementType("CHOICE_JOINED", null); + IElementType C_STATEMENT = new IElementType("C_STATEMENT", null); + IElementType EXPR = new IElementType("EXPR", null); + IElementType GRAMMAR_ELEMENT = new IElementType("GRAMMAR_ELEMENT", null); + IElementType IDENTIFIER = new IElementType("IDENTIFIER", null); + IElementType ID_EXPR = new IElementType("ID_EXPR", null); + IElementType INCLUDE_SECTION = new IElementType("INCLUDE_SECTION", null); + IElementType INCLUDE__SECTION__ALT = new IElementType("INCLUDE__SECTION__ALT", null); + IElementType ITEM_EXPR = new IElementType("ITEM_EXPR", null); + IElementType LEFT_SHADOW = new IElementType("LEFT_SHADOW", null); + IElementType LEFT_SHADOW_TEST = new IElementType("LEFT_SHADOW_TEST", null); + IElementType LITERAL = new IElementType("LITERAL", null); + IElementType MISSING_EXTERNAL_TYPE = new IElementType("MISSING_EXTERNAL_TYPE", null); + IElementType MUL_EXPR = new IElementType("MUL_EXPR", null); + IElementType PLUS_EXPR = new IElementType("PLUS_EXPR", null); + IElementType REF_EXPR = new IElementType("REF_EXPR", null); + IElementType ROOT = new IElementType("ROOT", null); + IElementType ROOT_B = new MyRootType("ROOT_B"); + IElementType ROOT_C = new MyRootType("ROOT_C"); + IElementType ROOT_D = new MyRootType("ROOT_D"); + IElementType SOME_EXPR = new IElementType("SOME_EXPR", null); + IElementType SPECIAL_REF = new IElementType("SPECIAL_REF", null); + IElementType STATEMENT = new IElementType("STATEMENT", null); + + IElementType ID = new IElementType("id", null); + IElementType NOTSPACE = new IElementType("notspace", null); + IElementType NUMBER = new IElementType("number", null); + IElementType OF = new IElementType("OF", null); + IElementType OP_DIV = new IElementType("/", null); + IElementType OP_MUL = new IElementType("*", null); + IElementType SLASH = new IElementType("\\", null); +} \ No newline at end of file diff --git a/testData/fleet/FleetPsiGen.bnf b/testData/fleet/FleetPsiGen.bnf new file mode 100644 index 00000000..d9232ef7 --- /dev/null +++ b/testData/fleet/FleetPsiGen.bnf @@ -0,0 +1,76 @@ +{ + classHeader="//header.txt" + parserClass="FleetPsiGen" + psiClassPrefix="X" + implements="XComposite" + parserUtilClass="PsiGenUtil" + extends(".*expr")=expr + extends("root_.")="root" + tokens=[OP_MUL="*" OP_DIV="/" SLASH='\' id='regexp:A' number='regexp:B' notspace='regexp:[^{}-]*'] + elementTypeClass("root_.*")="sample.MyRootType" + elementTypeFactory(".*_expr")="sample.MyTypeFactory.createExprType" + + methods("expr")=[missing] +} +grammar_root ::= root +root ::= root_a | root_b | root_c | root_d +external root_a ::= parseGrammar grammar_element +root_b ::= <> +root_c ::= <> +root_d ::= <> + +private meta listOf ::= <