欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ANTLR 学习

程序员文章站 2022-04-13 14:16:48
...

最近又开始尝试用ANTLR (another tool for language recognition) 来生成 DSL的Parser.  其实这个 ANTLR 东东用得很广泛的。  Hibernate 拿它来 parse   HQL,  而Spring 的 Expression Language 就是由它生成的parser 来完成的。  

我想用这个的初衷是来parser  Java 源代码。 想针对项目中涉及到的 比如由DAO 层的Hibernate  实现转换成JPA 实现来做些自动的工作。 这当中涉及到的要parse Java 源代码, 我们知道这个就是ANTLR的长项了。

 

废话少说, 开练。  ANTLR 的主要作用是生成 Lexer, Parser,  Tree Walker 或者 Lexer 跟Parser 的combinor.  主要的运作方式是 我们需要定义一个 grammar 文件。 在这个grammar 文件中 我们要指定:

 

          optionSpec

     tokensSpec

     attributeScopes

     actions

     rule1 : ...  | ;

     rule2 :      | ;

    这些部分的顺序是固定的,并且rule 在最后部分。

其实ANTLR 是吃它自己的狗粮的。我现在用的这个ANTLR 3.3 是由它的老版本2.7根据一个ANTLR.g grammar 文件来生成parser。由这个parser 来解析我们的grammar 文件,然后由它的另一个library StringTemplate 来生成我们的parser 或者lexer。 我们可以在它的官方网站上找到  https://github.com/antlr/antlr/blob/revision-3.4/tool/src/main/antlr3/org/antlr/grammar/v3/ANTLRv3.g

 

在这个 ANTLRv3.g 定义了它的start rule.

 

grammarDef

    :   DOC_COMMENT?

    ( 'lexer'  {gtype=LEXER_GRAMMAR;}    // pure lexer

    |   'parser' {gtype=PARSER_GRAMMAR;}   // pure parser

    |   'tree'   {gtype=TREE_GRAMMAR;}     // a tree parser

    |     {gtype=COMBINED_GRAMMAR;} // merged parser/lexer

    )

    g='grammar' id ';' optionsSpec? tokensSpec? attrScope* action*

    rule+

    EOF

    -> ^( {adaptor.create(gtype,$g)}

     id DOC_COMMENT? optionsSpec? tokensSpec? attrScope* action* rule+

    )

    ;

 

 

我们可以看到这里  最开始 我们可以指定点 comment,  然后是  grammar type  ; grammar id.  optionSpec (? 表示可选 ); tokenSpec(可选);  attrScope (可选); action (  * 表示 0 到多个); rule (+ 表示 1 到多个)。跟我们上面所说的各部分次序必须固定的说法是一致的。  

 

->  是 rewrite  AST 。 如果 我们在 options 里面指定  output=AST; ASTLabelType=CommonTree;

 

ANTLR 生成的parser的可读性也是很好的, 比起以前c 里面的lex, yy 好很多。 它采用的是递归下降的算法 会针对

每一个rule 都会生成 一个 function , 如果是最终符号会调用 match() 方法。 这样 一个rule 生成的方法差不多是

 

      public final TestParser.expr_return expr() throws RecognitionException {

        TestParser.expr_return retval = new TestParser.expr_return();
        retval.start = input.LT(1);

        Object root_0 = null;

        Token INT1=null;
        Token char_literal2=null;
        Token INT3=null;

        Object INT1_tree=null;
        Object char_literal2_tree=null;
        Object INT3_tree=null;

        try {
            // com\\bwang\\antlr\\Test.g:7:6: ( INT ( '+' INT )* )
            // com\\bwang\\antlr\\Test.g:7:8: INT ( '+' INT )*
            {
            root_0 = (Object)adaptor.nil();

            INT1=(Token)match(input,INT,FOLLOW_INT_in_expr21); 
            INT1_tree = (Object)adaptor.create(INT1);
            adaptor.addChild(root_0, INT1_tree);

            // com\\bwang\\antlr\\Test.g:7:12: ( '+' INT )*
            loop1:
            do {
                int alt1=2;
                switch ( input.LA(1) ) {
                case 5:
                    {
                    alt1=1;
                    }
                    break;

                }

                switch (alt1) {
            	case 1 :
            	    // com\\bwang\\antlr\\Test.g:7:13: '+' INT
            	    {
            	    char_literal2=(Token)match(input,5,FOLLOW_5_in_expr24); 
            	    char_literal2_tree = (Object)adaptor.create(char_literal2);
            	    root_0 = (Object)adaptor.becomeRoot(char_literal2_tree, root_0);

            	    INT3=(Token)match(input,INT,FOLLOW_INT_in_expr28); 
            	    INT3_tree = (Object)adaptor.create(INT3);
            	    adaptor.addChild(root_0, INT3_tree);


            	    }
            	    break;

            	default :
            	    break loop1;
                }
            } while (true);


            }

            retval.stop = input.LT(-1);

            retval.tree = (Object)adaptor.rulePostProcessing(root_0);
            adaptor.setTokenBoundaries(retval.tree, retval.start, retval.stop);

        }
        catch (RecognitionException re) {
            reportError(re);
            recover(input,re);
    	retval.tree = (Object)adaptor.errorNode(input, retval.start, input.LT(-1), re);

        }
        finally {
        }
        return retval;
    }
 

 

这个function 返回类型 TestParser.expr_return  是这样定义的 :

 

 

    public static class expr_return extends ParserRuleReturnScope {
        Object tree;
        public Object getTree() { return tree; }
    };

     当然如果我们指定ASTLabelType=CommonTree, 这个tree 就不是Object 而是  CommonTree 类型的了。

 

如果我们没有采用 -> 改写默认的AST 树,  它是一个 AST tree node 的list, 没有明显的层次。如果 我们通过  -> ^(rootNode, child1  child2 ....) 其中第一个节点是其他节点的root 节点。这样改写后就形成了比较强的层次的AST 树。

 

 

生成的代码是怎样用的呢 :

 

 

 

       CharStream stream = new ANTLRStringStream(uid);
        UdlLexer lexer = new UdlLexer(stream);
        TokenStream tokenStream = new CommonTokenStream(lexer);
        UdlParser parser = new UdlParser(tokenStream);
        return parser.uid();
 

  调用的顺序通常是 charStream ->  Lexer -> TokenStream -> parser  的开始rule 对应的方法。这个时候得到的就是一个AST 树。

 

 

相关标签: ANTLR PARSER