1. 程式人生 > >ANTLR教程(四)語法樹遍歷機制

ANTLR教程(四)語法樹遍歷機制

方法一: 使用antlr定義的語法樹遍歷順序——listener

antlr生成類ParseTreeListener,這個類裡面包含了進入語法樹種每個節點和退出每個節點時要進行的操作。本質上,就是樹的前序和後序遍歷。

類的繼承關係

這裡寫圖片描述

  1. ANTLR自動生成 XXXListener介面,在類xxxBaseListener中實現所有的方法
  2. 程式設計師需要做的是整合xxxxBaseListener類

需要與antlr遍歷類ParseTreeWalker一起使用

對同一非終結符的不同產生式進行標記

不標記的話實現起來複雜

listeners/Expr.g4
grammar Expr;
s:e;
e:eop=MULTe //MULTis’*’

e op=ADD e // ADD is ‘+’ INT

;

  1. listener需要判斷哪個e
    listeners/TestEvaluator.java
    public void exitE(ExprParser.EContext ctx) {
    if ( ctx.getChildCount()==3 ) { // operations have 3 children
    int left = values.get(ctx.e(0));
    int right = values.get(ctx.e(1));
    if ( ctx.op.getType()==ExprParser.MULT ) {
    values.put(ctx, left * right);
    }
    else {
    values.put(ctx, left + right);
    } }
    else {
    values.put(ctx, values.get(ctx.getChild(0))); // an INT
    } }
  2. 上下文EContext將三個可選擇的產生式放到了同一個上下文
    public static class EContext extends ParserRuleContext {
    public Token op;// derived from label op
    public List e() { … } */ get all e subtrees
    public EContext e(int i) { … } /* get ith e subtree
    public TerminalNode INT() { … } // get INT node if alt 3 of e

    }

解決方法: 標記產生式

e : e MULT e # Mult

e ADD e # Add
INT # Int

;

  1. ANTLR為每個產生式生成函式
    public interface LExprListener extends ParseTreeListener { void enterMult(LExprParser.MultContext ctx);
    void exitMult(LExprParser.MultContext ctx);
    void enterAdd(LExprParser.AddContext ctx);
    void exitAdd(LExprParser.AddContext ctx); void enterInt(LExprParser.IntContext ctx); void exitInt(LExprParser.IntContext ctx); …
    }
  2. ANTLR 生成特定的上下文物件,EContext的子類
  3. IntContext has only an INT() method

特點

  1. 程式設計師不需要顯示定義遍歷語法樹的順序,實現簡單
  2. 缺點,也是不能顯示控制遍歷語法樹的順序,visit方式可以
  3. 動作程式碼與文法產生式解耦,利於文法產生式的重用
  4. 沒有返回值,需要使用map、棧等結構在節點間傳值

例子

這裡寫圖片描述
語法樹遍歷時,函式呼叫順序
這裡寫圖片描述

方法二: 程式設計師需要自定義語法樹遍歷順序——visit

類的繼承關係

這裡寫圖片描述

特點

  1. 程式設計師可以顯示定義遍歷語法樹的順序
  2. 不需要與antlr遍歷類ParseTreeWalker一起使用,直接對tree操作
  3. 動作程式碼與文法產生式解耦,利於文法產生式的重用
  4. visitor方法可以直接返回值,返回值的型別必須一致,不需要使用map這種節點間傳值方式,效率高

例子

這裡寫圖片描述

PropertyFileVisitor loader = new PropertyFileVisitor();
loader.visit(tree);
System.out.println(loader.props); // print results

方法三: 將動作程式碼嵌入文法產生式文法

不利於文法產生式在不同的語言中重用