Сопоставление правил синтаксического анализа ANTLR с пользовательскими классами Java AST для генерации кода


Я, кажется, борюсь с AST - > StringTemplate стороной вещей, вероятно, потому что я пришел от написания парсеров вручную - > LLVM.

Я ищу способ автоматического сопоставления правила синтаксического анализа с классом AST, который может его представлять и содержит метод для генерации выходных данных целевого языка. (возможно, в этом случае используется StringTemplate.)

В псевдокоде, приведенном в этом примере грамматики:

numberExpression
    : DIGIT+
    ;

Я хочу, чтобы он был сопоставлен с этим АСТ класс:

class NumberExpressionAST extends BaseAST {
    private double value;

    public NumberExpressionAST(node) {
        this.value = node.value;
    }

    public String generateCode() {
        // However we want to generate the output.
        // Maybe use a template, maybe string literals, maybe cure cancer...whatever.
    }
}

Чтобы соединить их, возможно, будет какой-то клей, как показано ниже: (или вы можете сойти с ума с Class.forName вещами)

switch (child.ruleName) {
    case 'numberExpression':
        return new NumberExpressionAST(child);
        break;
}

Я рыскал по сети и нашел правила синтаксического разбора в грамматике с ->, но я не могу понять, как сохранить всю эту логику из грамматики. Особенно код для настройки и генерации целевого вывода из шаблона. Я не против ходить по дереву несколько раз.

Я подумал, что, возможно, я мог бы использовать этот вариант output=AST а потом, может быть, предоставить свои собственные классы AST, распространяющиеся от CommonTree? Я признаю, что мои познания в АНТЛР очень примитивны, так что простите мое невежество. Каждый урок, который я читаю, показывает, что все это делается в соответствии с грамматикой, которая для меня совершенно безумна и трудна для поддержания.

Может ли кто-нибудь указать мне на способ достижения чего-то подобного?

цель: исключить логику AST/codegen/template из грамматики.

Править ---------------------------------------------

Я прибегал к трассировке через фактический исходный код ANTLR (так как они используют себя), и я вижу похожие вещи, такие как BlockAST, RuleAST, и т.д. Все наследующие от CommonTree. Я еще не совсем разобрался в самом главном...как они их используют..

Оглядевшись, я заметил, что вы можете в основном вводить маркеры подсказок:

identifier
    : IDENTIFIER<AnyJavaClassIWantAST>
    ;

Вы не можете сделать то же самое для правил синтаксического анализа...но если вы создадите какой-то маркер для представления правило разбора в целом, вы можете использовать правила перезаписи следующим образом:

declaration
    : type identifier -> SOME_PARSE_RULE<AnyJavaClassIWantAST>
    ;

Все это ближе к тому, что я хочу, но в идеале я не должен мусорить grammar...is есть ли способ положить их куда-нибудь еще?

1 8

1 ответ:

Не могли бы вы добавить это в качестве ответа?..

Вот надуманный пример, который использует несколько функций ANTLR4, которые имеют большое значение для отделения грамматики от языка вывода, главным образомальтернативные метки и сгенерированный слушатель. Этот пример грамматики может представлять несколько тривиальных битов кода, но он делает это без ссылок на язык-даже без вызова skip() для пробелов в лексере. Тестовый класс преобразует входные данные в некоторые выходные данные Java использование сгенерированного слушателя.

Я избегал использовать что-либо, что я не мог получить, чтобы работать на первой паре попыток, так что не считайте это исчерпывающим примером ни в коем случае.

Симпланг.g

grammar Simplang;


compilationUnit : statements EOF;
statements      : statement+;
statement       : block #BlockStatement 
                | call  #CallStatement
                | decl  #DeclStatement
                ;
block           : LCUR statements RCUR;    
call            : methodName LPAR args=arglist? RPAR SEMI;
methodName      : ID;
arglist         : arg (COMMA arg)*;
arg             : expr;    
decl            : VAR variableName EQ expr SEMI;
variableName    : ID;
expr            : add_expr;     

add_expr        : lhs=primary_expr (add_op rhs=primary_expr)*;
add_op          : PLUS | MINUS;    
primary_expr    : string=STRING
                | id=ID
                | integer=INT
                ;    

VAR: 'var';   
ID: ('a'..'z'|'A'..'Z')+;
INT: ('0'..'9')+;
STRING: '\'' ~('\r'|'\n'|'\'')* '\'';
SEMI: ';';
LPAR: '(';
RPAR: ')';
LCUR: '{';
RCUR: '}';
PLUS: '+';
MINUS: '-';    
COMMA: ',';
EQ: '=';
WS: (' '|'\t'|'\f'|'\r'|'\n') -> skip;

Наряду с лексером и синтаксическим анализатором, ANTLR4 генерирует интерфейс прослушивателя и пустой класс реализации по умолчанию. Вот интерфейс, созданный для приведенной выше грамматики.

SimplangListener.java

public interface SimplangListener extends ParseTreeListener {
    void enterArglist(SimplangParser.ArglistContext ctx);
    void exitArglist(SimplangParser.ArglistContext ctx);
    void enterCall(SimplangParser.CallContext ctx);
    void exitCall(SimplangParser.CallContext ctx);
    void enterCompilationUnit(SimplangParser.CompilationUnitContext ctx);
    void exitCompilationUnit(SimplangParser.CompilationUnitContext ctx);
    void enterVariableName(SimplangParser.VariableNameContext ctx);
    void exitVariableName(SimplangParser.VariableNameContext ctx);
    void enterBlock(SimplangParser.BlockContext ctx);
    void exitBlock(SimplangParser.BlockContext ctx);
    void enterExpr(SimplangParser.ExprContext ctx);
    void exitExpr(SimplangParser.ExprContext ctx);
    void enterPrimary_expr(SimplangParser.Primary_exprContext ctx);
    void exitPrimary_expr(SimplangParser.Primary_exprContext ctx);
    void enterAdd_expr(SimplangParser.Add_exprContext ctx);
    void exitAdd_expr(SimplangParser.Add_exprContext ctx);
    void enterArg(SimplangParser.ArgContext ctx);
    void exitArg(SimplangParser.ArgContext ctx);
    void enterAdd_op(SimplangParser.Add_opContext ctx);
    void exitAdd_op(SimplangParser.Add_opContext ctx);
    void enterStatements(SimplangParser.StatementsContext ctx);
    void exitStatements(SimplangParser.StatementsContext ctx);
    void enterBlockStatement(SimplangParser.BlockStatementContext ctx);
    void exitBlockStatement(SimplangParser.BlockStatementContext ctx);
    void enterCallStatement(SimplangParser.CallStatementContext ctx);
    void exitCallStatement(SimplangParser.CallStatementContext ctx);
    void enterMethodName(SimplangParser.MethodNameContext ctx);
    void exitMethodName(SimplangParser.MethodNameContext ctx);
    void enterDeclStatement(SimplangParser.DeclStatementContext ctx);
    void exitDeclStatement(SimplangParser.DeclStatementContext ctx);
    void enterDecl(SimplangParser.DeclContext ctx);
    void exitDecl(SimplangParser.DeclContext ctx);
}

Вот тестовый класс, который переопределяет несколько методов в пустой слушатель и вызывает парсер.

SimplangTest.java

public class SimplangTest {

    public static void main(String[] args) {

        ANTLRInputStream input = new ANTLRInputStream(
                "var x = 4;\nfoo(x, 10);\nbar(y + 10 - 1, 'x' + 'y' + 'z');");

        SimplangLexer lexer = new SimplangLexer(input);

        SimplangParser parser = new SimplangParser(new CommonTokenStream(lexer));

        parser.addParseListener(new SimplangBaseListener() {
            public void exitArg(SimplangParser.ArgContext ctx) {
                System.out.print(", ");
            }

            public void exitCall(SimplangParser.CallContext call) {
                System.out.print("})");
            }

            public void exitMethodName(SimplangParser.MethodNameContext ctx) {
                System.out.printf("call(\"%s\", new Object[]{", ctx.ID()
                        .getText());
            }

            public void exitCallStatement(SimplangParser.CallStatementContext ctx) {
                System.out.println(";");
            }

            public void enterDecl(SimplangParser.DeclContext ctx) {
                System.out.print("define(");
            }

            public void exitVariableName(SimplangParser.VariableNameContext ctx) {
                System.out.printf("\"%s\", ", ctx.ID().getText());
            }

            public void exitDeclStatement(SimplangParser.DeclStatementContext ctx) {
                System.out.println(");");
            }

            public void exitAdd_op(SimplangParser.Add_opContext ctx) {
                if (ctx.MINUS() != null) {
                    System.out.print(" - ");
                } else {
                    System.out.print(" + ");
                }
            }

            public void exitPrimary_expr(SimplangParser.Primary_exprContext ctx) {
                if (ctx.string != null) {
                    String value = ctx.string.getText();
                    System.out.printf("\"%s\"",
                            value.subSequence(1, value.length() - 1));
                } else if (ctx.altNum == 2){    //cheating and using the alt# for "INT"
                    System.out.printf("read(\"%s\")", ctx.id.getText());
                } else {
                    System.out.print(ctx.INT().getText());
                }
            }
        });

        parser.compilationUnit();
    }
}

Вот тестовый вход, жестко закодированный в тестовом классе:

var x = 4;
foo(x, 10);
bar(y + 10 - 1, 'x' + 'y' + 'z');

Вот полученный результат:

define("x", 4);
call("foo", new Object[]{read("x"), 10, });
call("bar", new Object[]{read("y") + 10 - 1, "x" + "y" + "z", });
Это глупый пример, но он показывает несколько функций, которые могут быть полезны вам при создании пользовательского AST.