Skip to content

Add ability to use the generated parser instead of the source grammar #458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -64,6 +64,12 @@ because the interpreter is language agnostic.
For the same reasons, if your parser and/or lexer classes extend a custom implementation of the
base parser/lexer classes, your custom code will *not* be run during live preview.

As of 1.17, this limitation is partially not true. The configuration window of a grammar has a new option
that allows using the generated parser code in the preview. In this case, the grammar must be compiled into
a Java class (just to be on Project's target classpath). Any changes made to such grammar are not immediately
reflected in the preview and the project must be recompiled instead. It is also possible to specify the name of the
compiler parser/lexer class. By default the name of the grammar is used (parser and lexer grammar).

## History

See [Releases](https://github.com/antlr/intellij-plugin-v4/releases)
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -20,8 +20,8 @@ apply plugin: 'org.jetbrains.intellij'
apply plugin: 'antlr'

compileJava {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
sourceCompatibility = '11'
targetCompatibility = '11'
}

intellij {
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pluginVersion=1.19.2
pluginVersion=1.19.2-akovari

# e.g. IC-2016.3.3, IU-2018.2.5 etc
# For a list of possible values, refer to the section 'com.jetbrains.intellij.idea' at
@@ -16,7 +16,7 @@ pluginVersion=1.19.2

#ideaVersion=IC-2021.2

ideaVersion=IC-2021.3.3
ideaVersion=IC-2022.2

# The version of ANTLR v4 that will be used to generate the parser
antlr4Version=4.10.1
2 changes: 1 addition & 1 deletion historical-contributors-agreement.txt
Original file line number Diff line number Diff line change
@@ -64,4 +64,4 @@ YYYY/MM/DD, github id, Full name, email
2019/11/24, nopeslide, Uffke Drechsler, nopeslide@web.de
2020/12/05, roggenbrot, Sascha Dais, sdais@gmx.net
2021/11/04, OleksiiKovalov, Oleksii Kovalov, Oleksii.Kovalov@outlook.com

2021/06/04, akovari, Adam Kovari, kovariadam@gmail.com
987 changes: 522 additions & 465 deletions src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -78,6 +78,15 @@ public class ANTLRv4GrammarProperties implements Cloneable {
@OptionTag(converter = CaseChangingStrategyConverter.class)
CaseChangingStrategy caseChangingStrategy = CaseChangingStrategy.LEAVE_AS_IS;

@Property
boolean useGeneratedParserCodeCheckBox = false;

@Property
String generatedParserClassName;

@Property
String generatedLexerClassName;

public ANTLRv4GrammarProperties() {
}

@@ -92,6 +101,9 @@ public ANTLRv4GrammarProperties(ANTLRv4GrammarProperties source) {
this.generateListener = source.generateListener;
this.generateVisitor = source.generateVisitor;
this.caseChangingStrategy = source.caseChangingStrategy;
this.useGeneratedParserCodeCheckBox = source.useGeneratedParserCodeCheckBox;
this.generatedParserClassName = source.generatedParserClassName;
this.generatedLexerClassName = source.generatedLexerClassName;
}

public boolean shouldAutoGenerateParser() {
@@ -126,6 +138,18 @@ public boolean shouldGenerateParseTreeVisitor() {
return generateVisitor;
}

public boolean isUseGeneratedParserCodeCheckBox() {
return useGeneratedParserCodeCheckBox;
}

public String getGeneratedParserClassName() {
return generatedParserClassName;
}

public String getGeneratedLexerClassName() {
return generatedLexerClassName;
}

public CaseChangingStrategy getCaseChangingStrategy() {
return caseChangingStrategy;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.antlr.intellij.plugin.configdialogs.ConfigANTLRPerGrammar">
<grid id="27dc6" binding="dialogContents" layout-manager="GridLayoutManager" row-count="10" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="dialogContents" layout-manager="GridLayoutManager" row-count="13" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="630" height="292"/>
<xy x="20" y="20" width="630" height="403"/>
</constraints>
<properties/>
<border type="none"/>
@@ -109,11 +109,6 @@
</constraints>
<properties/>
</component>
<vspacer id="39a11">
<constraints>
<grid row="9" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="beca9" class="javax.swing.JLabel">
<constraints>
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="1" use-parent-layout="false"/>
@@ -128,6 +123,56 @@
</constraints>
<properties/>
</component>
<component id="8cd09" class="javax.swing.JCheckBox" binding="useGeneratedParserCodeCheckBox" default-binding="true">
<constraints>
<grid row="9" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="use generated parser code instead of grammar definition"/>
<toolTipText value="Live editing of grammar does not work with this option enabled. Every change in the grammar requires the project to be rebuilt."/>
</properties>
</component>
<vspacer id="39a11">
<constraints>
<grid row="12" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="523de" class="javax.swing.JTextField" binding="generatedParserClassName">
<constraints>
<grid row="10" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<enabled value="false"/>
</properties>
</component>
<component id="a48c0" class="javax.swing.JTextField" binding="generatedLexerClassName">
<constraints>
<grid row="11" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<enabled value="false"/>
</properties>
</component>
<component id="da6cb" class="javax.swing.JLabel">
<constraints>
<grid row="10" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Generated parser class name"/>
</properties>
</component>
<component id="a83b5" class="javax.swing.JLabel">
<constraints>
<grid row="11" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Generated lexer class name"/>
</properties>
</component>
</children>
</grid>
</form>
Original file line number Diff line number Diff line change
@@ -32,6 +32,9 @@ public class ConfigANTLRPerGrammar extends DialogWrapper {
protected JCheckBox autoGenerateParsersCheckBox;
protected JTextField languageField;
private JComboBox<CaseChangingStrategy> caseTransformation;
private JCheckBox useGeneratedParserCodeCheckBox;
private JTextField generatedParserClassName;
private JTextField generatedLexerClassName;

private ConfigANTLRPerGrammar(final Project project) {
super(project, false);
@@ -65,6 +68,15 @@ private void initAntlrFields(Project project, String qualFileName) {
libDirField.setTextFieldPreferredWidth(50);

loadValues(project, qualFileName);

useGeneratedParserCodeCheckBox.addActionListener(e -> toggleGeneratedClassNames());
toggleGeneratedClassNames();
}

private void toggleGeneratedClassNames() {
boolean enabled = useGeneratedParserCodeCheckBox.isSelected();
generatedParserClassName.setEnabled(enabled);
generatedLexerClassName.setEnabled(enabled);
}

public void loadValues(Project project, String qualFileName) {
@@ -79,6 +91,9 @@ public void loadValues(Project project, String qualFileName) {
caseTransformation.setSelectedItem(grammarProperties.getCaseChangingStrategy());
generateParseTreeListenerCheckBox.setSelected(grammarProperties.shouldGenerateParseTreeListener());
generateParseTreeVisitorCheckBox.setSelected(grammarProperties.shouldGenerateParseTreeVisitor());
useGeneratedParserCodeCheckBox.setSelected(grammarProperties.isUseGeneratedParserCodeCheckBox());
generatedParserClassName.setText(grammarProperties.generatedParserClassName);
generatedLexerClassName.setText(grammarProperties.generatedLexerClassName);
}

public void saveValues(Project project, String qualFileName) {
@@ -93,6 +108,9 @@ public void saveValues(Project project, String qualFileName) {
grammarProperties.caseChangingStrategy = getCaseChangingStrategy();
grammarProperties.generateListener = generateParseTreeListenerCheckBox.isSelected();
grammarProperties.generateVisitor = generateParseTreeVisitorCheckBox.isSelected();
grammarProperties.useGeneratedParserCodeCheckBox = useGeneratedParserCodeCheckBox.isSelected();
grammarProperties.generatedParserClassName = getGeneratedParserClassName();
grammarProperties.generatedLexerClassName = getGeneratedLexerClassName();
}

boolean isModified(ANTLRv4GrammarProperties originalProperties) {
@@ -101,7 +119,10 @@ boolean isModified(ANTLRv4GrammarProperties originalProperties) {
|| !Objects.equals(originalProperties.getEncoding(), getFileEncodingText())
|| !Objects.equals(originalProperties.getPackage(), getPackageFieldText())
|| !Objects.equals(originalProperties.getLanguage(), getLanguageText())
|| !Objects.equals(originalProperties.caseChangingStrategy, getCaseChangingStrategy());
|| !Objects.equals(originalProperties.caseChangingStrategy, getCaseChangingStrategy())
|| !Objects.equals(originalProperties.isUseGeneratedParserCodeCheckBox(), isUseGeneratedParserCode())
|| !Objects.equals(originalProperties.generatedParserClassName, getGeneratedParserClassName())
|| !Objects.equals(originalProperties.generatedLexerClassName, getGeneratedLexerClassName());
}

String getLanguageText() {
@@ -124,6 +145,18 @@ String getOutputDirText() {
return outputDirField.getText();
}

boolean isUseGeneratedParserCode() {
return useGeneratedParserCodeCheckBox.isSelected();
}

String getGeneratedParserClassName() {
return generatedParserClassName.getText();
}

String getGeneratedLexerClassName() {
return generatedLexerClassName.getText();
}

private CaseChangingStrategy getCaseChangingStrategy() {
return (CaseChangingStrategy) caseTransformation.getSelectedItem();
}
@@ -139,9 +172,12 @@ public String toString() {
return "ConfigANTLRPerGrammar{" +
" generateParseTreeListenerCheckBox=" + generateParseTreeListenerCheckBox +
", generateParseTreeVisitorCheckBox=" + generateParseTreeVisitorCheckBox +
", useGeneratedParserCodeCheckBox=" + useGeneratedParserCodeCheckBox +
", packageField=" + packageField +
", outputDirField=" + outputDirField +
", libDirField=" + libDirField +
", generatedParserClassName=" + generatedParserClassName +
", generatedLexerClassName=" + generatedLexerClassName +
'}';
}

100 changes: 60 additions & 40 deletions src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java
Original file line number Diff line number Diff line change
@@ -29,13 +29,14 @@
import org.antlr.v4.tool.ErrorType;
import org.antlr.v4.tool.Grammar;
import org.antlr.v4.tool.LexerGrammar;
import org.antlr.v4.tool.Rule;
import org.antlr.v4.tool.ast.GrammarRootAST;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import static org.antlr.intellij.plugin.configdialogs.ANTLRv4GrammarPropertiesStore.getGrammarProperties;
@@ -193,64 +194,83 @@ public static ParsingResult parseANTLRGrammar(String text) {
return new ParsingResult(parser, t, listener);
}

public static ParsingResult parseText(Grammar g,
LexerGrammar lg,
String startRuleName,
public static ParsingResult parseText(final Grammar g,
final LexerGrammar lg,
final Constructor<Lexer> lexerCtor,
final Constructor<Parser> parserCtor,
final String startRuleName,
final VirtualFile grammarFile,
String inputText,
Project project) {
final String inputText,
final Project project) {
if ( g==null || lg==null ) {
ANTLRv4PluginController.LOG.info("parseText can't parse: missing lexer or parser no Grammar object for " +
(grammarFile != null ? grammarFile.getName() : "<unknown file>"));
return null;
}

ANTLRv4GrammarProperties grammarProperties = getGrammarProperties(project, grammarFile);
CharStream input = grammarProperties.getCaseChangingStrategy()
.applyTo(CharStreams.fromString(inputText, grammarFile.getPath()));
LexerInterpreter lexEngine;
lexEngine = lg.createLexerInterpreter(input);
SyntaxErrorListener syntaxErrorListener = new SyntaxErrorListener();
lexEngine.removeErrorListeners();
lexEngine.addErrorListener(syntaxErrorListener);
CommonTokenStream tokens = new TokenStreamSubset(lexEngine);
return parseText(g, lg, startRuleName, grammarFile, syntaxErrorListener, tokens, 0);

try {
CommonTokenStream tokens;

LexerInterpreter lexEngine;
lexEngine = lg.createLexerInterpreter(input);
SyntaxErrorListener syntaxErrorListener = new SyntaxErrorListener();
lexEngine.removeErrorListeners();
lexEngine.addErrorListener(syntaxErrorListener);

if (lexerCtor != null) {
Lexer lexer = lexerCtor.newInstance(input);
tokens = new TokenStreamSubset(lexer);
} else {
tokens = new TokenStreamSubset(lexEngine);
}

Parser parser = null;

if (parserCtor != null) {
parser = parserCtor.newInstance(tokens);
}

return parseText(g, tokens, parser, syntaxErrorListener, startRuleName, 0);
} catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
ANTLRv4PluginController.LOG.error(e);
return null;
}
}

public static ParsingResult parseText(Grammar g,
LexerGrammar lg,
String startRuleName,
final VirtualFile grammarFile,
SyntaxErrorListener syntaxErrorListener,
TokenStream tokens,
Parser parser,
SyntaxErrorListener syntaxErrorListener,
String startRuleName,
int startIndex) {
if ( g==null || lg==null ) {
ANTLRv4PluginController.LOG.info("parseText can't parse: missing lexer or parser no Grammar object for " +
(grammarFile != null ? grammarFile.getName() : "<unknown file>"));
return null;
}
tokens.seek(startIndex);

String grammarFileName = g.fileName;
if (!new File(grammarFileName).exists()) {
ANTLRv4PluginController.LOG.info("parseText grammar doesn't exist "+grammarFileName);
return null;
}
ParserInterpreter parserInterpreter;

if ( g==BAD_PARSER_GRAMMAR || lg==BAD_LEXER_GRAMMAR ) {
return null;
if (parser != null) {
parserInterpreter = new PreviewParser(g, parser.getATN(), tokens);
} else {
parserInterpreter = new PreviewParser(g, tokens);
}

tokens.seek(startIndex);

PreviewParser parser = new PreviewParser(g, tokens);
parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
parser.setProfile(true);
parserInterpreter.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
parserInterpreter.setProfile(true);

parser.removeErrorListeners();
parser.addErrorListener(syntaxErrorListener);
parserInterpreter.removeErrorListeners();
parserInterpreter.addErrorListener(syntaxErrorListener);

Rule start = g.getRule(startRuleName);
if ( start==null ) {
int startRuleIndex = parserInterpreter.getRuleIndex(startRuleName);
if ( startRuleIndex==-1 ) {
return null; // can't find start rule
}
ParseTree t = parser.parse(start.index);
ParseTree t = parserInterpreter.parse(startRuleIndex);

if ( t!=null ) {
return new ParsingResult(parser, t, syntaxErrorListener);
return new ParsingResult(parserInterpreter, t, syntaxErrorListener);
}
return null;
}
Original file line number Diff line number Diff line change
@@ -5,9 +5,13 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.antlr.intellij.plugin.parsing.ParsingResult;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.tool.Grammar;
import org.antlr.v4.tool.LexerGrammar;

import java.lang.reflect.Constructor;

/** Track everything associated with the state of the preview window.
* For each grammar, we need to track an InputPanel (with <= 2 editor objects)
* that we will flip to every time we come back to a specific grammar,
@@ -33,6 +37,9 @@ public class PreviewState {

public ParsingResult parsingResult;

public Constructor<Parser> parserCtor;
public Constructor<Lexer> lexerCtor;

/** The current input editor (inputEditor or fileEditor) for this grammar
* in InputPanel. This can be null when a PreviewState and InputPanel
* are created out of sync. Depends on order IDE opens files vs