【问题标题】:Mapping ANTLR parse rules to custom Java AST classes for code generation将 ANTLR 解析规则映射到自定义 Java AST 类以生成代码
【发布时间】:2012-11-14 15:59:41
【问题描述】:

我似乎在处理 AST->StringTemplate 方面的问题,可能是因为我是从手工编写解析器开始的 -> LLVM。

我正在寻找一种将解析规则自动匹配到可以表示它的 AST 类的方法,并包含一种生成目标语言输出的方法。 (在这种情况下,可能使用 StringTemplate。)

在伪代码中,给出这个示例语法:

numberExpression
    : DIGIT+
    ;

我想让它映射到这个 AST 类:

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,然后提供我自己的从 CommonTree 扩展而来的 AST 类?我承认,我对 ANTLR 的掌握非常原始,所以请原谅我的无知。我遵循的每个教程都显示所有这些内容都与对我完全疯狂且难以维护的语法内联。

有人能指点我完成类似事情的方法吗?

目标:将 AST/codegen/template 逻辑排除在语法之外。

编辑 ----------------------------------------------

我求助于追踪 ANTLR 的实际源代码(因为它们使用自己),我看到类似的东西,如 BlockASTRuleAST 等,都继承自 CommonTree。我还没有弄清楚重要的部分......他们是如何使用它们的......

环顾四周,我注意到您基本上可以输入提示标记:

identifier
    : IDENTIFIER<AnyJavaClassIWantAST>
    ;

你不能对解析规则做完全相同的事情......但是如果你创建一些标记来代表整个解析规则,你可以使用这样的重写规则:

declaration
    : type identifier -> SOME_PARSE_RULE<AnyJavaClassIWantAST>
    ;

所有这些都更接近我想要的,但理想情况下我不应该乱扔语法......有没有办法把这些放在其他地方?

【问题讨论】:

  • “目标:将 AST/codegen/template 逻辑排除在语法之外...... [我]实际上我不应该乱扔语法......”听起来你想要所有的好处ANTLR 没有任何 ANTLR 的好处。 ;) 我认为您唯一真正的选择是编写自己的语法解析器,按照自己的方式进行操作,或者硬着头皮使用设计的 ANTLR:使用生成的代码,在语法中指定 AST 类型,等等。跨度>
  • 我明白你的意思,但也许我的“目标”有点过于直白了。 ANTLR 肯定不仅仅是被解析的语法语法,所以我肯定想利用它的其他特性,但是从实际语法规则本身进行某种程度的抽象会很好。我认为我发现的 identifier : IDENTIFIER&lt;AnyJavaClassIWantAST&gt; ; 功能非常适合我。
  • 如果您愿意切换到 ANTLR 4,您可能会使用它的alternative labels 更接近您的目标,这会变成由生成的代码触发的侦听器事件。在这一点上,我对它们的了解还不够,无法给出完整的答案,但它看起来确实是一个很好的语言中立抽象层。
  • @tenterhook 非常酷。您能否将此添加为答案,我会很乐意接受吗?谢谢!

标签: java parsing antlr llvm abstract-syntax-tree


【解决方案1】:

你能添加这个作为答案吗...

这是一个人为的示例,它使用了一些 ANTLR4 的功能,这些功能大大有助于将语法与输出语言分开,主要是 alternative labels 和生成的侦听器。这个示例语法可以表示一些微不足道的代码,但它没有语言参考——甚至没有调用 skip() 来获取词法分析器中的空格。测试类使用生成的侦听器将输入转换为一些 Java 输出。

我避免使用我在前几次尝试中无法使用的任何东西,所以无论如何不要认为这是一个详尽的例子。

Simplang.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 时可能对您有用的功能。

【讨论】:

  • @BartKiers 你太客气了。 :D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多