Как мне красиво печатать продукцию и номера строк, используя ANTLR4?
Я пытаюсь написать кусок кода, который будет принимать синтаксический анализатор ANTLR4 и использовать его для генерации AST для входных данных, аналогичных тем, которые задаются параметром -tree в grun (misc.TestRig
). Тем не менее, я бы хотел, чтобы выходные данные включали всю информацию о номере строки / смещении.
Например, вместо печати
(add (int 5) '+' (int 6))
Я хотел бы получить
(add (int 5 [line 3, offset 6:7]) '+' (int 6 [line 3, offset 8:9]) [line 3, offset 5:10])
Или что-то подобное.
Для ANTLR4 пока нет огромного количества примеров посетителей, но я уверен, что смогу сделать большую часть этого, скопировав реализацию по умолчанию для toStringTree
(используется Грюном). Однако я не вижу никакой информации о номерах строк или смещениях.
Я ожидал, что смогу написать супер простой код, подобный этому:
String visit(ParseTree t) {
return "(" + t.productionName + t.visitChildren() + t.lineNumber + ")";
}
но не все так просто. Я предполагаю, что смогу получить информацию о номере строки от парсера, но я не понял, как это сделать. Как я могу получить эту информацию о номере строки / смещении в моем обходе?
Чтобы заполнить несколько пробелов в приведенном ниже решении, я использовал:
List<String> ruleNames = Arrays.asList(parser.getRuleNames());
parser.setBuildParseTree(true);
ParserRuleContext prc = parser.program();
ParseTree tree = prc;
чтобы получить tree
и ruleNames
, program
это название для лучшего производства в моей грамматике.
1 ответ
Trees.toStringTree
метод может быть реализован с использованием ParseTreeListener
, Следующий слушатель производит точно такой же вывод, как Trees.toStringTree
,
public class TreePrinterListener implements ParseTreeListener {
private final List<String> ruleNames;
private final StringBuilder builder = new StringBuilder();
public TreePrinterListener(Parser parser) {
this.ruleNames = Arrays.asList(parser.getRuleNames());
}
public TreePrinterListener(List<String> ruleNames) {
this.ruleNames = ruleNames;
}
@Override
public void visitTerminal(TerminalNode node) {
if (builder.length() > 0) {
builder.append(' ');
}
builder.append(Utils.escapeWhitespace(Trees.getNodeText(node, ruleNames), false));
}
@Override
public void visitErrorNode(ErrorNode node) {
if (builder.length() > 0) {
builder.append(' ');
}
builder.append(Utils.escapeWhitespace(Trees.getNodeText(node, ruleNames), false));
}
@Override
public void enterEveryRule(ParserRuleContext ctx) {
if (builder.length() > 0) {
builder.append(' ');
}
if (ctx.getChildCount() > 0) {
builder.append('(');
}
int ruleIndex = ctx.getRuleIndex();
String ruleName;
if (ruleIndex >= 0 && ruleIndex < ruleNames.size()) {
ruleName = ruleNames.get(ruleIndex);
}
else {
ruleName = Integer.toString(ruleIndex);
}
builder.append(ruleName);
}
@Override
public void exitEveryRule(ParserRuleContext ctx) {
if (ctx.getChildCount() > 0) {
builder.append(')');
}
}
@Override
public String toString() {
return builder.toString();
}
}
Класс можно использовать следующим образом:
List<String> ruleNames = ...;
ParseTree tree = ...;
TreePrinterListener listener = new TreePrinterListener(ruleNames);
ParseTreeWalker.DEFAULT.walk(listener, tree);
String formatted = listener.toString();
Класс можно изменить для получения информации в выходных данных, обновив exitEveryRule
метод:
@Override
public void exitEveryRule(ParserRuleContext ctx) {
if (ctx.getChildCount() > 0) {
Token positionToken = ctx.getStart();
if (positionToken != null) {
builder.append(" [line ");
builder.append(positionToken.getLine());
builder.append(", offset ");
builder.append(positionToken.getStartIndex());
builder.append(':');
builder.append(positionToken.getStopIndex());
builder.append("])");
}
else {
builder.append(')');
}
}
}