package com.github.jknack.antlr4ide.runtime;
import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import org.antlr.v4.Tool;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.LexerInterpreter;
import org.antlr.v4.runtime.ParserInterpreter;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.misc.Nullable;
import org.antlr.v4.runtime.misc.Utils;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.RuleNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.antlr.v4.runtime.tree.Tree;
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;
public class ParseTreeCommand {
private PrintWriter out;
public ParseTreeCommand(final PrintWriter out) {
this.out = out;
}
public String run(final String grammarFileName, final String lexerFileName,
final String outdir, final List<String> imports, final String startRule,
final String inputText) {
Tool antlr = new Tool();
if (imports.size() > 0) {
antlr.inputDirectory = new File(imports.iterator().next()).getParentFile();
}
antlr.libDirectory = outdir;
// load to examine it
Grammar g = loadGrammar(antlr, grammarFileName, null);
final LexerGrammar lg;
if (g instanceof LexerGrammar) {
lg = (LexerGrammar) g;
} else if (lexerFileName != null) {
lg = (LexerGrammar) antlr.loadGrammar(lexerFileName);
g = loadGrammar(antlr, grammarFileName, lg);
} else {
// combined?
lg = g.getImplicitLexer();
}
final String gname = g.name;
BaseErrorListener printError = new BaseErrorListener() {
@Override
public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol,
final int line, final int position, final String msg,
final RecognitionException e) {
out.println(gname + "::" + startRule + ":" + line + ":" + position + ": " + msg);
}
};
ANTLRInputStream input = new ANTLRInputStream(inputText);
LexerInterpreter lexEngine = (lg == null? g : lg).createLexerInterpreter(input);
lexEngine.removeErrorListeners();
lexEngine.addErrorListener(printError);
CommonTokenStream tokens = new CommonTokenStream(lexEngine);
ParserInterpreter parser = g.createParserInterpreter(tokens);
parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
parser.removeErrorListeners();
parser.addErrorListener(printError);
Rule start = g.getRule(startRule);
ParseTree tree = parser.parse(start.index);
// this loop works around a bug in ANTLR 4.2
// https://github.com/antlr/antlr4/issues/461
// https://github.com/antlr/intellij-plugin-v4/issues/23
while (tree.getParent() != null) {
tree = tree.getParent();
}
String sexpression = toStringTree(tree, Arrays.asList(parser.getRuleNames())).trim();
return sexpression;
}
/** Same as loadGrammar(fileName) except import vocab from existing lexer */
private Grammar loadGrammar(final Tool tool, final String fileName,
final LexerGrammar lexerGrammar) {
GrammarRootAST grammarRootAST = tool.parseGrammar(fileName);
final Grammar g = tool.createGrammar(grammarRootAST);
g.fileName = fileName;
if (lexerGrammar != null) {
g.importVocab(lexerGrammar);
}
tool.process(g, false);
return g;
}
private String toStringTree(@NotNull final Tree t, @Nullable final List<String> ruleNames) {
if (t.getChildCount() == 0) {
return Utils.escapeWhitespace(getNodeText(t, ruleNames), true);
}
StringBuilder buf = new StringBuilder();
buf.append(" ( ");
String s = Utils.escapeWhitespace(getNodeText(t, ruleNames), true);
buf.append(s);
buf.append(' ');
for (int i = 0; i < t.getChildCount(); i++) {
if (i > 0) {
buf.append(' ');
}
buf.append(toStringTree(t.getChild(i), ruleNames));
}
buf.append(" ) ");
return buf.toString();
}
private String getNodeText(@NotNull final Tree t, @Nullable final List<String> ruleNames) {
if (ruleNames != null) {
if (t instanceof RuleNode) {
int ruleIndex = ((RuleNode) t).getRuleContext().getRuleIndex();
String ruleName = ruleNames.get(ruleIndex);
return ruleName;
}
else if (t instanceof ErrorNode) {
return "<" + t.toString() + ">";
}
else if (t instanceof TerminalNode) {
Token symbol = ((TerminalNode) t).getSymbol();
if (symbol != null) {
String s = symbol.getText();
return "'" + s + "'";
}
}
}
// no recog for rule names
Object payload = t.getPayload();
if (payload instanceof Token) {
return ((Token) payload).getText();
}
return t.getPayload().toString();
}
}