Как мне красиво распечатать продукцию и номера строк, используя 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 (используется grun). Однако я не вижу никакой информации о номерах строк или смещениях.

Я ожидал, что смогу написать такой супер простой код:

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 - это название лучшей продукции в моей грамматике.


person Chucky Ellison    schedule 13.10.2013    source источник
comment
Есть 2 toStringTree метода. Один принимает Parser экземпляр, а другой просто List<String> имен правил.   -  person Sam Harwell    schedule 14.10.2013
comment
@ 280Z28: Вы утверждаете правдивый факт. Вызов toStringTree с аргументом парсера заставляет реализацию захватить список правил (recog.getRuleNames()) и передать его toStringTree, который принимает List. Во всяком случае, это все еще не объясняет, как получить информацию о номере строки / смещении при написании посетителя.   -  person Chucky Ellison    schedule 14.10.2013


Ответы (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(')');
        }
    }
}
person Sam Harwell    schedule 14.10.2013
comment
Это отлично сработало. Я обновляю вопрос, чтобы заполнить несколько пробелов. - person Chucky Ellison; 14.10.2013