package org.antlr.intellij.adaptor.parser; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.psi.tree.IElementType; import org.antlr.intellij.adaptor.lexer.PSITokenSource; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.TokenSource; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.jetbrains.annotations.NotNull; /** An adaptor that makes an ANTLR parser look like a PsiParser. */ public abstract class ANTLRParserAdaptor implements PsiParser { protected final Language language; protected final Parser parser; /** Create a jetbrains adaptor for an ANTLR parser object. When * the IDE requests a {@link #parse(IElementType, PsiBuilder)}, * the token stream will be set on the parser. */ public ANTLRParserAdaptor(Language language, Parser parser) { this.language = language; this.parser = parser; } public Language getLanguage() { return language; } @NotNull @Override public ASTNode parse(IElementType root, PsiBuilder builder) { ProgressIndicatorProvider.checkCanceled(); TokenSource source = new PSITokenSource(builder); TokenStream tokens = new CommonTokenStream(source); parser.setTokenStream(tokens); parser.setErrorHandler(new ErrorStrategyAdaptor()); // tweaks missing tokens parser.removeErrorListeners(); parser.addErrorListener(new SyntaxErrorListener()); // trap errors ParseTree parseTree = null; PsiBuilder.Marker rollbackMarker = builder.mark(); try { parseTree = parse(parser, root); } finally { rollbackMarker.rollbackTo(); } // Now convert ANTLR parser tree to PSI tree by mimicking subtree // enter/exit with mark/done calls. I *think* this creates their parse // tree (AST as they call it) when you call {@link PsiBuilder#getTreeBuilt} ANTLRParseTreeToPSIConverter listener = createListener(parser, root, builder); PsiBuilder.Marker rootMarker = builder.mark(); ParseTreeWalker.DEFAULT.walk(listener, parseTree); while (!builder.eof()) { ProgressIndicatorProvider.checkCanceled(); builder.advanceLexer(); } // NOTE: parse tree returned from parse will be the // usual ANTLR tree ANTLRParseTreeToPSIConverter will // convert that to the analogous jetbrains AST nodes // When parsing an entire file, the root IElementType // will be a IFileElementType. // // When trying to rename IDs and so on, you get a // dummy root and a type arg identifier IElementType. // This results in a weird tree that has for example // (ID (expr (primary ID))) with the ID IElementType // as a subtree root as well as the appropriate leaf // all the way at the bottom. The dummy ID root is a // CompositeElement and created by // ParserDefinition.createElement() despite having // being TokenIElementType. rootMarker.done(root); return builder.getTreeBuilt(); // calls the ASTFactory.createComposite() etc... } protected abstract ParseTree parse(Parser parser, IElementType root); protected ANTLRParseTreeToPSIConverter createListener(Parser parser, IElementType root, PsiBuilder builder) { return new ANTLRParseTreeToPSIConverter(language, parser, builder); } }