BITMINICC——利用Antlr的Listener生成AST

BITMINICC生成AST

首先请花一定的时间阅读下面的参考方法,本文是我对下面文章的一个理解和踩坑记录。

参考方法:https://zhuanlan.zhihu.com/p/369250644?utm_source=qq&utm_medium=social&utm_oi=732535204933025792

1.一个好的文法

因为要用Antlr生成Lexer以及Listener,所以一个定义完整的文法是最重要的。因为当你用一个缺陷的文法,重写好了Listener,分析好了上一个实验的测试用例,结果到了下个实验直接连语法树都没办法生成的时候,就得一切重来(没错,这就是我写了3次语法分析的原因)

2.Antlr的Listener遍历方式

打开你使用Antlr工具生成的BaseListener.java,里面是对你文法的每一个节点的Enter和Exit函数,当调用Listener遍历你的语法树时,Walker会调用语法树的每一个节点的Enter函数直到Terminalnode,再从下往上调用Exit函数退出。这也是参考方法的原理。

怎么使用Listener方法

Listener需要与walker一起使用

ParseTree tree = parser.compilationUnit();
ParseTreeWalker walker = new ParseTreeWalker();
MyListener listener = new MyListener();//你重载的类
walker.walk(listener, tree);

3.要挂载哪些节点?

BITMINICC中定义好了实验所需的AST类,总结来说其中包括:

程序定义ASTCompilationUnit,声明语句ASTDeclaration,函数定义ASTFunctionDefine,声明ASTDeclarator,表达式ASTExpression,句子ASTStatement。其中ASTDeclarator,ASTExpression,ASTStatement为抽象类,被其它具体的节点所继承,详细如下。

/**
 * Declarator
 *@JsonSubTypes.Type(value = ASTArrayDeclarator.class,name = "ArrayDeclarator"),
@JsonSubTypes.Type(value = ASTVariableDeclarator.class,name = "VariableDeclarator"),
@JsonSubTypes.Type(value = ASTFunctionDeclarator.class,name="FunctionDeclarator"),
@JsonSubTypes.Type(value = ASTParamsDeclarator.class,name="ASTParamsDeclarator"),
 */
/**
 * Expression
 *    @JsonSubTypes.Type(value = ASTIdentifier.class,name = "Identifier"),
 *    @JsonSubTypes.Type(value = ASTArrayAccess.class,name = "ArrayAccess"),
 *    @JsonSubTypes.Type(value = ASTBinaryExpression.class,name="BinaryExpression"),
 *    @JsonSubTypes.Type(value = ASTCastExpression.class,name = "CastExpression"),
 *    @JsonSubTypes.Type(value = ASTCharConstant.class,name = "CharConstant"),                  UNDO
 *    @JsonSubTypes.Type(value = ASTConditionExpression.class,name = "ConditionExpression"),    UNDO
 *    @JsonSubTypes.Type(value = ASTFloatConstant.class,name = "FloatConstant"),                UNDO
 *    @JsonSubTypes.Type(value = ASTFunctionCall.class,name = "FunctionCall"),
 *    @JsonSubTypes.Type(value = ASTIntegerConstant.class,name = "IntegerConstant"),
 *    @JsonSubTypes.Type(value = ASTMemberAccess.class,name = "MemberAccess"),                  UNDO
 *    @JsonSubTypes.Type(value = ASTPostfixExpression.class,name = "PostfixExpression"),
 *    @JsonSubTypes.Type(value = ASTStringConstant.class,name = "StringConstant"),
 *    @JsonSubTypes.Type(value = ASTUnaryExpression.class,name = "UnaryExpression"),
 *    @JsonSubTypes.Type(value = ASTUnaryTypename.class,name = "UnaryTypename")                 UNDO
 */
/**
 * Statement
 *    @JsonSubTypes.Type(value = ASTBreakStatement.class,name = "BreakStatement"),
 *    @JsonSubTypes.Type(value = ASTCompoundStatement.class,name = "CompoundStatement"),
 *    @JsonSubTypes.Type(value = ASTContinueStatement.class,name="ContinueStatement"),
 *    @JsonSubTypes.Type(value = ASTExpressionStatement.class,name = "ExpressionStatement"),
 *    @JsonSubTypes.Type(value = ASTGotoStatement.class,name = "GotoStatement"),
 *    @JsonSubTypes.Type(value = ASTIterationDeclaredStatement.class,name = "IterationDeclaredStatement"),
 *    @JsonSubTypes.Type(value = ASTIterationStatement.class,name = "IterationStatement"),
 *    @JsonSubTypes.Type(value = ASTLabeledStatement.class,name = "LabeledStatement"),
 *    @JsonSubTypes.Type(value = ASTReturnStatement.class,name = "ReturnStatement"),
 *    @JsonSubTypes.Type(value = ASTSelectionStatement.class,name = "SelectionStatement")
 */

所以其实就5类节点需要挂载,相同类的实现基本相同。

4.怎么挂载?

如参考文章中所说,我们需要用一个栈,在访问到某一个具体节点时压入,退出某一个具体节点时弹出并挂载。那么最大的问题就是从哪一个节点访问和退出?毕竟语法分析树存在的递归实在太多了。

我的方法也是和参考文章一样,将递归的节点定义为pass,具有具体意义的节点定义为需要实现的。

postfixExpression

    :   primaryExpression                                   #postfixExpression_pass

    |   postfixExpression '[' expression ']'                #postfixExpression_arrayaccess

    |   postfixExpression '(' argumentExpressionList? ')'   #postfixExpression_funccall

    |   postfixExpression '.' Identifier                    #postfixExpression_member

    |   postfixExpression '->' Identifier                   #postfixExpression_point

    |   postfixExpression '++'                              #postfixExpression_

    |   postfixExpression '--'                              #postfixExpression_

    |   '(' typeName ')' '{' initializerList '}'            #postfixExpression_pass

    |   '(' typeName ')' '{' initializerList ',' '}'        #postfixExpression_pass

    |   '__extension__' '(' typeName ')' '{' initializerList '}'    #postfixExpression_pass

    |   '__extension__' '(' typeName ')' '{' initializerList ',' '}'    #postfixExpression_pass

    ;

 

这是一个后缀表达式的文法,最开始的primaryExpression很明显就是一个递归节点定义为pass,而后面的4个是不会用到的,也定义为pass。

(忘了说了,生成的Listener如果你有定义的话,就会生成对应的函数,比如这个例子就会生成如下的函数接口,但是其实用到的也就arrayaccess,funcall,_,3个)

@Override public void enterPostfixExpression_arrayaccess(MyCGrammerParser.PostfixExpression_arrayaccessContext ctx) { }

@Override public void exitPostfixExpression_arrayaccess(MyCGrammerParser.PostfixExpression_arrayaccessContext ctx) { }

@Override public void enterPostfixExpression_point(MyCGrammerParser.PostfixExpression_pointContext ctx) { }

@Override public void exitPostfixExpression_point(MyCGrammerParser.PostfixExpression_pointContext ctx) { }

@Override public void enterPostfixExpression_funcall(MyCGrammerParser.PostfixExpression_funcallContext ctx) { }

@Override public void exitPostfixExpression_funcall(MyCGrammerParser.PostfixExpression_funcallContext ctx) { }

@Override public void enterPostfixExpression_pass(MyCGrammerParser.PostfixExpression_passContext ctx) { }

@Override public void exitPostfixExpression_pass(MyCGrammerParser.PostfixExpression_passContext ctx) { }

@Override public void enterPostfixExpression_member(MyCGrammerParser.PostfixExpression_memberContext ctx) { }

@Override public void exitPostfixExpression_member(MyCGrammerParser.PostfixExpression_memberContext ctx) { }

@Override public void enterPostfixExpression_(MyCGrammerParser.PostfixExpression_Context ctx) { }

@Override public void exitPostfixExpression_(MyCGrammerParser.PostfixExpression_Context ctx) { }

 

说回怎么挂载,这样定义之后,在你生成的语法树中,如果是形如postfixExpression '++'(a++)的节点,在语法树中会是下面这样

"assignmentExpression_pass": [
  {
    "conditionalExpression": [
      {
        "logicalOrExpression_pass": [
          {
            "logicalAndExpression_pass": [
              {
                "inclusiveOrExpression_pass": [
                  {
                    "exclusiveOrExpression_pass": [
                      {
                        "andExpression_pass": [
                          {
                            "equalityExpression_pass": [
                              {
                                "relationalExpression_pass": [
                                  {
                                    "shiftExpression_pass": [
                                      {
                                        "additiveExpression_pass": [
                                          {
                                            "multiplicativeExpression_pass": [
                                              {
                                                "castExpression_pass": [
                                                  {
                                                    "unaryExpression_pass": [
                                                      {
                                                        "postfixExpression_": [
                                                          {
                                                            "postfixExpression_pass": [
                                                              {
                                                                "primaryExpression": [
                                                                  {
                                                                    "tokenId": [
                                                                      {
                                                                        "value": "a1",
                                                                        "tokenId": 101
                                                                      }
                                                                    ]
                                                                  }
                                                                ]
                                                              }
                                                            ]
                                                          },
                                                          {
                                                            "type": "BlockComment",
                                                            "value": "++",
                                                            "tokenId": 68
                                                          }

可以观察到除了有一个节点是"postfixExpression_"之外,其余的都是pass。("conditionalExpression"和"primaryExpression"是因为我没有对这两个文法重新定义)

这个时候,你应该已经懂了,我们只需要重写EnterpostfixExpression_和ExitpostfixExpression_即可。

在Enter时创建一个节点,在Exit时把它挂到正确的ParentNode上去。

5.挂到哪?

正如我说的,BITMINICC中定义的AST类其实就5类,同类之间的挂载是相同的,所以你只需要比对每一个节点它的成员需要哪一类,就能把哪一类挂到它上面去。

@JsonTypeName("PostfixExpression")
public class ASTPostfixExpression extends ASTExpression{

   public ASTExpression expr;
   public ASTToken op;
   
   public ASTPostfixExpression() {
      super("PostfixExpression");
   }
   public ASTPostfixExpression(ASTExpression expr,ASTToken op){
      super("PostfixExpression");
      this.expr = expr;
      this.op = op;
   }
   @Override
   public void accept(ASTVisitor visitor) throws Exception {
      visitor.visit(this);
   }

}

"PostfixExpression"节点需要的就是ASTExpression和ASTToken,当你退出一个ASTExpression节点时,检测栈里的上一个节点的类型是不是"PostfixExpression",如果是,就挂到它的上面去。

if(ParentNode.getClass()==ASTPostfixExpression.class){
    if(((ASTPostfixExpression) ParentNode).expr==null){
        ((ASTPostfixExpression) ParentNode).expr=(ASTExpression) node;
    }
}

6.踩的一些坑

AST类中,有的是List,有的是变量,List如果是null需要先分配空间。

AST类中自带了.children.add()和.children.addAll()方法,在退出节点时,需要将节点的成员都挂到children上才能生成JSON

可以考虑在挂载节点时,顺带把node.parent也挂上,方便以后访问树。

7.

最后,语法分析生成AST树是语义分析和中间代码生成的先决条件,做好语法分析需要一个好的文法。

我写了3次语法分析,第1次没有生成AST,第2次生成的AST只能适用于实验5的例子,第3次写的时候我就将实验6,7,8的例子作为用例来进行测试,至少现在是符合要求了,所以你在写的时候,最好也把实验8的例子加进来测试。

至于这次的实验,在你有一颗正确的AST树时,就是对这棵树进行访问和取你需要的东西,虽然也很恶心,但至少不会没有思路,以上。

(肝了2天3000行的屎山)

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值