iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 6
0
Software Development

做一個JVM語言系列 第 6

Antlr:Listener監聽者模式

  • 分享至 

  • xImage
  •  

昨天講到Antlr尋訪(Visit)模式。這個功能在實務上比較少用到,尤其是在做工具時。
不管是Listener監聽者模式或是尋訪(Visit)模式,基本上Antlr只要把抽象語法樹(abstract syntax tree或者縮寫為AST) 產生出來,它就能在每一個節點停留,產生對應文法檔(.g4)裏的元素的事件。
我們只要在事件裏寫CODE. 就能把一些不可思議的工具做出來。

在官方手冊的第四章,就有一個從Java class 裏,逆產生 interface 宣告的例子。
很多程式員,終其一生不會碰到這個問題。或是只要用別人開發出來的重構(REFACTOR)工具來解決即可。
問題:給定一個Java class,如

import java.util.List;
import java.util.Map;
public class Demo {
	void f(int x, String y) { }
	int[ ] g(/*no args*/) { return null; }
	List<Map<String, Integer>>[] h() { return null; }
}

請產生該CLASS的介面(interface),如

interface IDemo {
	void f(int x, String y);
	int[ ] g(/*no args*/);
	List<Map<String, Integer>>[] h();
}

有些人可能會用正規表達示的來find/replace.
把public class 替換成 interface,
把Demo 替換成IDemo,這時開始有問題了,在{ 左邊是 類別名,....
看起來簡單,做起來有點問題。

如果透過Antlr的Listener監聽者模式,那會如何解呢?
首先編一個Java的文法檔。
!@#$%%%%%,蝦米,這是我等程式員,做的到的事嗎?
現成的Java的文法檔,BNF檔,符合Antlr的(.g4)格式嗎?
幸好,在開放源始碼的精神帶動下,
世界的語言愛好者,自動自發在Parr教授的帶領下,
維護了很多語言的文法檔
其中也包括Antlr 自己的BNF文法檔。好像是一種遞迴的關係。
或是雞生蛋,蛋生雞的問題,到底是先有Antlr,還是先有文法呢?

標準的JAVA 1.6版文法,有700 多行文法。
以本案需求,只需要對幾個點,如CLASS類別名,裏面的方法名,方法的實作要去掉,但方法的參數要照抄,
把這些方法挑出來,

一樣的指令,
$ antlr4 Java.g4

產生對應程式碼,

public interface JavaListener extends ParseTreeListener {
..................
	/**
	 * Enter a parse tree produced by {@link JavaParser#classDeclaration}.
	 * @param ctx the parse tree
	 */
	void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx);
	/**
	 * Exit a parse tree produced by {@link JavaParser#classDeclaration}.
	 * @param ctx the parse tree
	 */
	void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx);
.........................
............................
.........................
	/**
	 * Enter a parse tree produced by {@link JavaParser#methodDeclaration}.
	 * @param ctx the parse tree
	 */
	void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx);
	/**
	 * Exit a parse tree produced by {@link JavaParser#methodDeclaration}.
	 * @param ctx the parse tree
	 */
	void exitMethodDeclaration(JavaParser.MethodDeclarationContext ctx);
...............................................

這是Antlr 從文法檔中產生的CODE, 對應

grammar Java;
........................................
classDeclaration
    :   'class' Identifier typeParameters? ('extends' type)?
        ('implements' typeList)?
        classBody
    ;
.....................................
methodDeclaration
    :   type Identifier formalParameters ('[' ']')* methodDeclarationRest
    |   'void' Identifier formalParameters methodDeclarationRest
    ;
.....................................

對應來看,可看出 Antlr生成程式的命名規則,就是文法裏有classDeclaration 元素,Antlr
就生成兩個方法,enterClassDeclaration,exitClassDeclaration。
7百多行的JAVA文法檔裏,每個元素都會產生,enterXXXXXX,exitXXXXXX, 從字面上來看,
就是在enter元素、exit元素時,觸發事件。
我們就在事件裏寫CODE.

以本案例,
就寫如下的CODE,

import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;

public class ExtractInterfaceListener extends JavaBaseListener {
    JavaParser parser;
    public ExtractInterfaceListener(JavaParser parser) {this.parser = parser;}
    /** Listen to matches of classDeclaration */
    @Override
    public void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx){
        System.out.println("interface I"+ctx.Identifier()+" {");
    }
    @Override
    public void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx) {
        System.out.println("}");
    }

    /** Listen to matches of methodDeclaration */
    @Override
    public void enterMethodDeclaration(
        JavaParser.MethodDeclarationContext ctx
    )
    {
        // need parser to get tokens
        TokenStream tokens = parser.getTokenStream();
        String type = "void";
        if ( ctx.type()!=null ) {
            type = tokens.getText(ctx.type());
        }
        String args = tokens.getText(ctx.formalParameters());
        System.out.println("\t"+type+" "+ctx.Identifier()+args+";");
    }
}

我們實作一個ExtractInterfaceListener監聽者 ,擴展JavaBaseListener介面。
覆寫(Override)我們需要的方法(事件)。
一進入(ENTER)到類別宣告時,請產生
System.out.println("interface I"+ctx.Identifier()+" {");
程式中寫的很直白。ctx.Identifier()是類別的名稱,前面加上"interface I"…,

文法檔裏的 type, Identifier, formalParameters,這些元素,都可以在程式中被使用。
只要Method 的宣告,就用";"結束,不要{}裏的程式內容。

基本上,Listener監聽者模式,就能實作強大的語言轉換器。
Parr教授累積數十年的語言解析功力,提出一套方法論,
只要文法寫的沒問題,Antlr 就能產生 按此文法 寫出的程式的解析程式碼,
而這工具產生的解析程式碼,基本上是產生抽象文法樹,遍尋所有節點,
Listener監聽每個節點,產生對應的EVENT,喜歡做語言轉換工具者,
就在這些EVENT裏,做轉換。

廣泛的類C語言,彼此之間的轉換,基本上有一半的CODE不用置換。像if , for , 宣告變數的,基本上不用變。
但從類C語言,換其他語言,變動就較大了。

有一些主打能省CODE的行數的新語言,有時候可用這種方式來更充分理解為什麼能省CODE.

當這類工具一產生,把從前只有高手才寫的出來的LEXER/PARSER 手寫CODE.
變得普通程式員可用工具做掉。這個算是時代的進步。對新語言的產生也有推波助瀾的作用。
Antlr 默默的委身在一些爆紅的,劃時代創新專案裏,如ORM的關鍵成型代表:Hibernate.
基本上也是用另一種語言來代替SQL.結果所有的後端框架都要ORM了。沒ORM就不算一個正常的框架,
這是基本配備。


上一篇
Antlr:文法檔(.g4)練習之二,工程用計算機把值算出來
下一篇
Antlr練習,解析CSV檔
系列文
做一個JVM語言12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言