ANTLR:从CommonTree到有用的对象图

时间:2011-03-09 21:32:13

标签: java antlr

我今天开始使用ANTLR并且我已经创建了一个基本的解析器。

解析后我最后得到了一棵树。对我来说,这似乎只是一堆String放在Tree - 节点的树结构中。这对我来说不是很有用。我想要一个对象图。

澄清(这是一个例子,而不是我的真实应用):对于"5-1+6"我似乎最终得到:

new String("PLUS")
    new String("MINUS")
        new String("5")
        new String("1")
    new String("6")

我觉得更有用:

new Plus(
    new Minus(
        new IntegerLiteral(5),
        new IntegerLiteral(1)),
    new IntegerLiteral(6))

从第一个表示到另一个表示最方便的方法是什么?在this article中,作者做了类似的事情:

public Expression createExpr(CommonTree ast) {

    // ...

    switch (ast.getType()) {
    case SimpleExpressionParser.INT:
        return new IntegerLiteral(ast.getText())
    case SimpleExpressionParser.PLUS:
        return new Plus(createExpr((CommonTree)ast.getChild(0)),    // recurse
                        createExpr((CommonTree)ast.getChild(1)));   // recurse
    case SimpleExpressionParser.MINUS:
        return new Minus(createExpr((CommonTree)ast.getChild(0)),   // recurse
                         createExpr((CommonTree)ast.getChild(1)));  // recurse
    }

    // ...
}

这是首选方式吗?!我不能指示ANTLR以某种方式生成这个样板代码(它会很大)吗?


可能相关的问题:

1 个答案:

答案 0 :(得分:21)

这是一种可能的方式。简而言之,这些是您要执行的步骤:

  1. 创建一个组合语法,生成词法分析器和解析器;
  2. 在(1)的语法中混合AST重写规则,将令牌的平面列表转换为适当的树;
  3. 写一个可以从(2);
  4. 走树的树语法
  5. 在树步行者中混合自定义代码;
  6. 测试它。
  7. 1

    让我们创建一个支持+-*/(...)和数字的小型表达式解析器,它们看起来像:

    grammar Exp; // file: Exp.g
    
    eval
      :  exp EOF
      ;
    
    exp 
      :  addExp 
      ;
    
    addExp
      :  mulExp ((Add | Sub) mulExp)*
      ;
    
    mulExp
      :  unaryExp ((Mul | Div) unaryExp)*
      ;
    
    unaryExp
      :  Sub atom
      |  atom
      ;
    
    atom
      :  Number
      |  '(' exp ')'
      ;
    
    Add    : '+';
    Sub    : '-';
    Mul    : '*';
    Div    : '/';
    Number : '0'..'9'+;
    Space  : ' ' {skip();};
    

    2

    包括重写规则,它将如下所示:

    grammar Exp; // file: Exp.g
    
    options {
      output=AST;
    }
    
    tokens {
      U_SUB;
    }
    
    eval 
      :  exp EOF -> exp
      ;
    
    exp 
      :  addExp 
      ;
    
    addExp
      :  mulExp ((Add | Sub)^ mulExp)*
      ;
    
    mulExp
      :  unaryExp ((Mul | Div)^ unaryExp)*
      ;
    
    unaryExp
      :  Sub atom -> ^(U_SUB atom)
      |  atom
      ;
    
    atom
      :  Number
      |  '(' exp ')' -> exp
      ;
    
    Add    : '+';
    Sub    : '-';
    Mul    : '*';
    Div    : '/';
    Number : '0'..'9'+;
    Space  : ' ' {skip();};
    

    现在,10 - 2 * (3 + 8)之类的表达式将转换为:

    enter image description here

    3

    要创建一个为(2)中生成的AST生成迭代器的树语法,你可以这样做:

    tree grammar ExpWalker; // file: ExpWalker.g
    
    options {
      tokenVocab=Exp; // use the tokens from Exp.g
      ASTLabelType=CommonTree;
    }
    
    eval
      :  exp
      ;
    
    exp
      :  ^(Add exp exp)
      |  ^(Sub exp exp)
      |  ^(Mul exp exp)
      |  ^(Div exp exp)
      |  ^(U_SUB exp)
      |  Number
      ;
    

    4

    要在这个树迭代器中混合自定义类,请执行以下操作:

    tree grammar ExpWalker; // file: ExpWalker.g
    
    options {
      tokenVocab=Exp; // use the tokens from Exp.g
      ASTLabelType=CommonTree;
    }
    
    eval returns [ExpNode e]
      :  exp {e = $exp.e;}
      ;
    
    exp returns [ExpNode e]
      :  ^(Add a=exp b=exp) {e = new AddExp($a.e, $b.e);}
      |  ^(Sub a=exp b=exp) {e = new SubExp($a.e, $b.e);}
      |  ^(Mul a=exp b=exp) {e = new MulExp($a.e, $b.e);}
      |  ^(Div a=exp b=exp) {e = new DivExp($a.e, $b.e);}
      |  ^(U_SUB a=exp)     {e = new UnaryExp($a.e);}
      |  Number             {e = new NumberExp($Number.text);}
      ;
    

    5

    以下是测试所有类的一些代码(仅将其粘贴在一个文件中:Main.java):

    import org.antlr.runtime.*;
    import org.antlr.runtime.tree.*;
    import org.antlr.stringtemplate.*;
    
    public class Main {
      public static void main(String[] args) throws Exception {
        String source = "10 - 2 * (3 + 8)";
        ExpLexer lexer = new ExpLexer(new ANTLRStringStream(source));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        ExpParser.eval_return returnValue = parser.eval();
        CommonTree tree = (CommonTree)returnValue.getTree();
        CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree);
        ExpWalker walker = new ExpWalker(nodes);
        ExpNode root = walker.eval();
        System.out.println(source + " = " + root.evaluate());
      }
    }
    
    interface ExpNode {
      double evaluate();
    }
    
    class NumberExp implements ExpNode {
    
      final double num;
    
      NumberExp(String s) {
        num = Double.parseDouble(s);
      }
    
      @Override
      public double evaluate() {
        return num;
      }
    }
    
    class AddExp implements ExpNode {
    
      final ExpNode left, right;
    
      AddExp(ExpNode a, ExpNode b) {
        left = a;
        right = b;
      }
    
      @Override
      public double evaluate() {
        return left.evaluate() + right.evaluate();
      }
    }
    
    class SubExp implements ExpNode {
    
      final ExpNode left, right;
    
      SubExp(ExpNode a, ExpNode b) {
        left = a;
        right = b;
      }
    
      @Override
      public double evaluate() {
        return left.evaluate() - right.evaluate();
      }
    }
    
    class MulExp implements ExpNode {
    
      final ExpNode left, right;
    
      MulExp(ExpNode a, ExpNode b) {
        left = a;
        right = b;
      }
    
      @Override
      public double evaluate() {
        return left.evaluate() * right.evaluate();
      }
    }
    
    class DivExp implements ExpNode {
    
      final ExpNode left, right;
    
      DivExp(ExpNode a, ExpNode b) {
        left = a;
        right = b;
      }
    
      @Override
      public double evaluate() {
        return left.evaluate() / right.evaluate();
      }
    }
    
    class UnaryExp implements ExpNode {
    
      final ExpNode exp;
    
      UnaryExp(ExpNode e) {
        exp = e;
      }
    
      @Override
      public double evaluate() {
        return -exp.evaluate();
      }
    }
    

    然后执行:

    # generate a lexer & parser
    java -cp antlr-3.2.jar org.antlr.Tool Exp.g
    
    # generate the tree walker
    java -cp antlr-3.2.jar org.antlr.Tool ExpWalker.g
    
    # compile everything
    javac -cp antlr-3.2.jar *.java
    
    # run the main class
    java -cp .:antlr-3.2.jar Main         # *nix 
    java -cp .;antlr-3.2.jar Main         # Windows
    

    打印:

    10 - 2 * (3 + 8) = -12.0
    

    你可以跳过树行走者并将所有代码和returns [...]混合在你的组合语法中,但是IMO,一个树语法使事情更加有序,因为词法分析器规则,以及(等标记)等已被删除。

    HTH