ANTLR 4 и AST посетители

Я пытаюсь использовать АСТ с ANTLR4, с этими файлами:

Builder.java

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.TokenStream;

public class Builder
{

    public static void main(String[] args)
    {
        CharStream input = new ANTLRInputStream("ON M1==2 && M3 == 5 && (M2 > 1 || M5 <= 5.0) "
                                              + "DO P5:42 P4:10");
        ExprLexer lexer = new ExprLexer(input);
        TokenStream tokens = new CommonTokenStream(lexer);
        ExprParser parser = new ExprParser(tokens);
        parser.addParseListener(new ExprTestListener());
        ExprParser.ExpressionContext uu = parser.expression();
    }

}

ExprTestListener:

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.antlr.v4.runtime.tree.ErrorNode;

public class ExprTestListener extends ExprBaseListener {
    @Override public void enterExpression(ExprParser.ExpressionContext ctx)
    {
        System.out.println(ctx);
    }
    @Override public void exitExpression(ExprParser.ExpressionContext ctx)
    {
        System.out.println(ctx);
    }

    @Override public void enterActionexpr(ExprParser.ActionexprContext ctx)
    {
        System.out.println(ctx);
    }
    @Override public void exitActionexpr(ExprParser.ActionexprContext ctx)
    {
        System.out.println(ctx);
    }

    @Override public void enterCondexpr(ExprParser.CondexprContext ctx)
    {
        System.out.println(ctx);
    }
    @Override public void exitCondexpr(ExprParser.CondexprContext ctx)
    {
        System.out.println(ctx);
    }

    @Override public void enterCond(ExprParser.CondContext ctx)
    {
        System.out.println(ctx);
    }
    @Override public void exitCond(ExprParser.CondContext ctx)
    {
        System.out.println(ctx);
    }

    @Override public void enterEveryRule(ParserRuleContext ctx)
    {
        System.out.println(ctx);
    }
    @Override public void exitEveryRule(ParserRuleContext ctx)
    {
        System.out.println(ctx);
    }
    @Override public void visitTerminal(TerminalNode node)
    {
    }
    @Override public void visitErrorNode(ErrorNode node)
    {
    }
}

Expr.g:

grammar Expr;
options
{
  // antlr will generate java lexer and parser
  language = Java;

}
WS      : [ \t\r\n]+ -> skip ;
OP      : '&&' | '||';
COMP    : '==' | '<' | '>' | '<=' | '>=' | '!=';
fragment INT     : [0-9]+;
REAL    : INT '.' INT | INT;

ACTION  : 'P' INT ':' INT;
MEASURE : 'M' INT;

// ***************** parser rules:
cond       : MEASURE COMP REAL;
condexpr   : '(' condexpr ')' | cond OP condexpr | cond;
actionexpr : ACTION actionexpr | ACTION;
expression : 'ON' condexpr 'DO' actionexpr;

У меня есть этот вывод:

[]
[]
[29]
[29]
[16 29]
[16 29]
[16 29]
[16 29]
[18 29]
[18 29]
[16 18 29]
[16 18 29]
[16 18 29]
[16 18 29]
[18 18 29]
[18 18 29]
[13 18 18 29]
[13 18 18 29]
[16 13 18 18 29]
[16 13 18 18 29]
[16 13 18 18 29]
[16 13 18 18 29]
[18 13 18 18 29]
[18 13 18 18 29]
[20 18 13 18 18 29]
[20 18 13 18 18 29]
[20 18 13 18 18 29]
[20 18 13 18 18 29]
[18 13 18 18 29]
[18 13 18 18 29]
[13 18 18 29]
[13 18 18 29]
[18 18 29]
[18 18 29]
[18 29]
[18 29]
[29]
[29]
[31]
[31]
[24 31]
[24 31]
[24 31]
[24 31]
[31]
[31]
[]
[]

Мне трудно понять посетителей с ANTLR4.

У меня есть цели дерева:

  • Получить коллекцию INT MEASURE и ACTION (в двух разных наборах)
  • Замените некоторые OP (например!= by < > )
  • Получить строку condexpr (верхний элемент) с перезапущенными OPs (по моей предыдущей точке)

Ответ 1

Древовидная метка, которую вы можете использовать, чтобы задать контекст синтаксического анализа, а затем просматривает листья наблюдаемого графика с помощью класса посетителя и методов триггера, чтобы создавать действия из выражений в исходном коде языка. Таким образом, при первом посещении слушатель не обрабатывает фактический шаблон посетителя. Фактический шаблон посетителя и обработка с помощью посещения выполняются с помощью методов расширения класса слушателя expressionbase.

Слушатель идентифицирует выражение:

@Override public void enterListener(ExprParser.EXPR_CONTEXTContext ctx) { 
  //some code to view the compilation process
}

Правило выражения получает метку имени:

'EXPR_CONTEXT' expression             # EXPR_CONTEXT //the tree label

Выражается ходок:

public class ExprWalker extends ExprBaseListener {

  @Override 
  public void enterListener(ExprParser.EXPR_CONTEXTContext ctx) { 

    java.util.List<ExprParser.ExpressionContext> e = ctx.expression();

    System.out.println("EXPRESSION: " //print action
    + e.get(0).getText() + ", " //first element
    + e.get(1).getText() //second element
    + ", " + ... + ", " //number of elements
    + e.get(N).getText()); //last element

}

Главный файл затем идет с ходоком:

ParseTree tree = parser.parse(); //parse the tree

intermezzo: перед тем, как применить шаблон посещения ходока, можно представить оптимизацию или обработку шаблонов деревьев. Обработанное дерево может обрабатываться здесь как отдельная индукция дерева исходного кода. Этот подход позволяет более сложные шаблоны обработки кода и дерева.

ParseTreeWalker walker = new ParseTreeWalker(); //get the walker
walker.walk(new ExprWalker(), tree); //start visiting

Ответ 2

Сначала я объясню, что вы наблюдали выше:

Прежде всего, пожалуйста, прочитайте документацию для методов, которые вы вызываете. Документация Parser.addParseListener содержит следующее примечание:

ЭТО ТОЛЬКО ДЛЯ ПЕРЕДОВЫХ ПОЛЬЗОВАТЕЛЕЙ. Пожалуйста, дайте ParseTreeListener ParseTreeWalker вместо того, чтобы давать его парсеру!!!!

Реализация toString() для класса ParserRuleContext просто печатает стеки вызовов правил во время создания контекста. Вы печатаете это один раз, когда слушатель вводит правило, и один раз, когда он выходит. Для actionexpr, cond и condexpr вы снова распечатываете его, в результате получается в общей сложности 4 идентичные строки вывода для каждого из этих контекстов.

Теперь некоторые заметки о ваших целях:

  • Внутри enterCond и exitCond текст MEASURE доступен, вызывая ctx.MEASURE().getText().
  • Внутри enterActionexpr и exitActionexpr текст ACTION доступен, вызывая ctx.ACTION().getText().
  • Вы можете изменить токен cond, создав новый TerminalNodeImpl и CommonToken для обновленного токена и присвоив его правильному индексу в поле CondContext.children, используя либо посетителя, либо слушателя.