antlr4操作入門(java版本)

背景

最近在學習github上的一個mlsql項目的時候穆端,發(fā)現(xiàn)了antlr這一強大的語言解析工具袱贮。上網(wǎng)搜羅了很多資料,基本都是概念原理之類体啰,示例也比較單一攒巍,看了之后難以上手。為了幫助初次接觸antlr的童鞋們能夠快速運用antlr做出東西來狡赐,遂出此文窑业,希望能幫助到迷茫中的朋友。(本人渣渣一枚枕屉,沒有什么語言解析的基礎(chǔ)常柄,僅僅幫助大家使用工具,不談原理)

概要

本文參照mlsql搀擂,定義一種數(shù)據(jù)加載規(guī)則西潘,使用antlr,實現(xiàn)spark加載各種數(shù)據(jù)源的功能

環(huán)境準備

環(huán)境:java8+maven+idea
插件:安裝idea-antlr4的插件(file-->setting-->plugins-->install plugin from disk) 插件下載

antlr前端

一些概念

  • 前端:定義語法規(guī)則哨颂,antlr通過g4文件來定義
  • lexer:詞法解規(guī)則喷市,就是將一個句子多個字符進行組裝分成多個單詞的規(guī)則
  • parser:語法解析,對分詞后的整個句子進行解析威恼,可以對每個分詞單元做出自定義的處理品姓,從而來實現(xiàn)自己的語法解析功能。

g4文件

g4文件是antlr生成詞法解析規(guī)則和語法解析規(guī)則的基礎(chǔ)箫措。該文件是我們自定義的腹备,文件名后綴需要是.g4。g4文件的結(jié)構(gòu)大致為:

  • grammar
  • comment(同java //)
  • options
  • import
  • tokens
  • @actionName
  • rule
    我們需要關(guān)注的主要是grammar與rule

grammar

grammar是規(guī)則文件的頭斤蔓,需要與文件名保持一致植酥。當antlr生成詞法語法解析的規(guī)則代碼時,類名就是根據(jù)grammar的名字來的弦牡。

rule

rule是antlr生成詞法語法解析的基礎(chǔ)友驮。包括了lexer與parser,每條規(guī)則都是key:value的形式驾锰,以分號結(jié)尾卸留。lexer首字母大寫,lexer小寫椭豫。

g4文件的編寫與解釋

grammar Dsl;    //定義規(guī)則文件grammar
@header {        //一種action,定義生成的詞法語法解析文件的頭耻瑟,當使用java的時候买喧,生成的類需要包名,可以在這里統(tǒng)一定義
 package antlr;
 }

//parsers
sta:(sql ender)*;  //定義sta規(guī)則匆赃,里面包含了*(0個以上)個 sql ender組合規(guī)則
ender:';';  //定義ender規(guī)則,是一個分號
sql   //定義sql規(guī)則今缚,sql規(guī)則有兩條分支:select/load
    : SELECT ~(';')* as tableName   //select語法規(guī)則算柳,以lexer SELECT開頭, 以as tableName 結(jié)尾姓言,其中as 和tableName分別是兩個parser
    | LOAD format '.' path  as tableName //load語法規(guī)則,大致就是 load json.'path' as table1瞬项,load語法里面含有format,path何荚, as囱淋,tableName四種規(guī)則
    ;    //sql規(guī)則結(jié)束符
as: AS;   //定義as規(guī)則,其內(nèi)容指向AS這個lexer
tableName: identifier;  //tableName 規(guī)則餐塘,指向identifier規(guī)則
format: identifier;   //format規(guī)則妥衣,也指向identifier規(guī)則
path: quotedIdentifier; //path,指向quotedIdentifier 
identifier: IDENTIFIER | quotedIdentifier;  //identifier,指向lexer IDENTIFIER  或者parser quotedIdentifier
quotedIdentifier: BACKQUOTED_IDENTIFIER;  //quotedIdentifier,指向lexer BACKQUOTED_IDENTIFIER

//lexers antlr將某個句子進行分詞的時候戒傻,分詞單元就是如下的lexer
//keywords  定義一些關(guān)鍵字的lexer税手,忽略大小寫
AS: [Aa][Ss];
LOAD: [Ll][Oo][Aa][Dd];
SELECT: [Ss][Ee][Ll][Ee][Cc][Tt];

//base  定義一些基礎(chǔ)的lexer,
fragment DIGIT:[0-9];   //匹配數(shù)字
fragment LETTER:[a-zA-Z];  //匹配字母
STRING        //匹配帶引號的文本
    : '\'' ( ~('\''|'\\') | ('\\' .) )* '\''
    | '"' ( ~('"'|'\\') | ('\\' .) )* '"'
    ;
IDENTIFIER    //匹配只含有數(shù)字字母和下劃線的文本
    : (LETTER | DIGIT | '_')+
    ;
BACKQUOTED_IDENTIFIER   //匹配被``包裹的文本
    : '`' ( ~'`' | '``' )* '`'
    ;

//--hiden  定義需要隱藏的文本,指向channel(HIDDEN)就會隱藏需纳。這里的channel可以自定義芦倒,到時在后臺獲取不同的channel的數(shù)據(jù)進行不同的處理
SIMPLE_COMMENT: '--' ~[\r\n]* '\r'? '\n'? -> channel(HIDDEN);   //忽略行注釋
BRACKETED_EMPTY_COMMENT: '/**/' -> channel(HIDDEN);  //忽略多行注釋
BRACKETED_COMMENT : '/*' ~[+] .*? '*/' -> channel(HIDDEN) ;  //忽略多行注釋
WS: [ \r\n\t]+ -> channel(HIDDEN);  //忽略空白符

// 匹配其他的不能使用上面的lexer進行分詞的文本
UNRECOGNIZED: .;

插件配置生成代碼

  • 創(chuàng)建一個maven項目
  • 將Dsl.g4文件放入項目中
  • 配置antlr插件的config


    configure

    configure
  • 生成代碼


    generate

    generate

生成代碼解釋

  • DslLexer 詞法解析類
  • DslParser 語法解析類,在類中有各種Context,每個parser都賭對應(yīng)了一個xxxContext的內(nèi)部類不翩,在Context中記錄了與其他Context的包含關(guān)系兵扬,還提供了獲取parser中的lexer的方法,以及進出這個rule的回調(diào)函數(shù)
  • DslListener 語法解析監(jiān)聽器口蝠。antlr有l(wèi)istener和visitor兩種遍歷方式器钟,前面配置的時候選擇的是listener,因此只生成了listener亚皂。 在Listener中提供了進入和退出每一種規(guī)則的回調(diào)方法俱箱。我們可以通過實現(xiàn)Listtener類,按需覆寫回調(diào)方法灭必,以此來實現(xiàn)我們的業(yè)務(wù)狞谱。

antlr后端

簡單使用

  • 添加依賴
<dependency>
     <groupId>org.antlr</groupId>
     <artifactId>antlr4-runtime</artifactId>
     <version>4.7.1</version>
</dependency>
  • 打印解析樹
    public static void main(String[] args) throws IOException {
        String sql= "Select 'abc' as a, `hahah` as c  From a aS table;";
        ANTLRInputStream input = new ANTLRInputStream(sql);  //將輸入轉(zhuǎn)成antlr的input流
        DslLexer lexer = new DslLexer(input);  //詞法分析
        CommonTokenStream tokens = new CommonTokenStream(lexer);  //轉(zhuǎn)成token流
        DslParser parser = new DslParser(tokens); // 語法分析
        DslParser.StaContext tree = parser.sta();  //獲取某一個規(guī)則樹,這里獲取的是最外層的規(guī)則禁漓,也可以通過sql()獲取sql規(guī)則樹......
        System.out.println(tree.toStringTree(parser)); //打印規(guī)則數(shù)
    }

load語法實現(xiàn)

功能解說

load的語法: load json.'F:\tmp\user' as temp; 通過類似的語法跟衅,實現(xiàn)spark加載文件夾的數(shù)據(jù),然后將數(shù)據(jù)注冊成一張表播歼。這里的json可以替換為spark支持的文件格式伶跷。

實現(xiàn)思路

如load json.'F:\tmp\user' as temp這樣一個sql,對應(yīng)了我們自定義規(guī)則的sql規(guī)則里面的load分支掰读。 load-->LOAD,json-->format叭莫,'F:\tmp\user' -->path蹈集, as-->as,temp--> tableName雇初。
我們可以通過覆寫Listener的enterSql()方法拢肆,來獲取到sql規(guī)則里面,與之相關(guān)聯(lián)的其他元素靖诗,獲取到各個元素的內(nèi)容郭怪,通過spark來根據(jù)不同的內(nèi)容加載不同的數(shù)據(jù)。

實現(xiàn)代碼

public class ParseListener extends DslBaseListener {
    @Override
    public void enterSql(DslParser.SqlContext ctx) {
        String keyword = ctx.children.get(0).getText();  //獲取sql規(guī)則的第一個元素刊橘,為select或者load
        if("select".equalsIgnoreCase(keyword)){
            execSelect(ctx);   //第一個元素為selece的時候執(zhí)行select
        }else if("load".equalsIgnoreCase(keyword)){
            execLoad(ctx);  //第一個元素為load的時候執(zhí)行l(wèi)oad
        }

    }
    public void execLoad(DslParser.SqlContext ctx){
        List<ParseTree> children = ctx.children;   //獲取該規(guī)則樹的所有子節(jié)點
        String format = "";
        String path = "";
        String tableName = "";
        for (ParseTree c :children) {
            if(c instanceof DslParser.FormatContext){
                format = c.getText();
            }else if(c instanceof DslParser.PathContext){
                path = c.getText().substring(1,c.getText().length()-1);
            }else if(c instanceof DslParser.TableNameContext){
                tableName = c.getText();
            }
        }
        System.out.println(format);
        System.out.println(path);
        System.out.println(tableName);
        // spark load實現(xiàn)鄙才,省略
    }

    public void execSelect(DslParser.SqlContext ctx){

    }

    public static void main(String[] args) throws IOException {
        String len = "load json.`F:\\tmp\\user` as temp;";
        ANTLRInputStream input = new ANTLRInputStream(len);
        DslLexer lexer = new DslLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        DslParser parser = new DslParser(tokens);
        DslParser.SqlContext tree = parser.sql();
        ParseListener listener = new ParseListener();
        ParseTreeWalker.DEFAULT.walk(listener,tree);  //規(guī)則樹遍歷
    }
}

ps:由于近期使用,只是大致調(diào)試整理了下促绵,僅僅只是為了方便初接觸的朋友快速用起來攒庵,要深入就要靠自己了,可能有很多錯誤和見解疏漏的地方绞愚,還請大家莫要介意叙甸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市位衩,隨后出現(xiàn)的幾起案子裆蒸,更是在濱河造成了極大的恐慌,老刑警劉巖糖驴,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僚祷,死亡現(xiàn)場離奇詭異,居然都是意外死亡贮缕,警方通過查閱死者的電腦和手機辙谜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來感昼,“玉大人装哆,你說我怎么就攤上這事《ㄉぃ” “怎么了蜕琴?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宵溅。 經(jīng)常有香客問我凌简,道長,這世上最難降的妖魔是什么恃逻? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任雏搂,我火速辦了婚禮藕施,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凸郑。我一直安慰自己裳食,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布芙沥。 她就那樣靜靜地躺著胞谈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憨愉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天卿捎,我揣著相機與錄音配紫,去河邊找鬼。 笑死午阵,一個胖子當著我的面吹牛躺孝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播底桂,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼植袍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了籽懦?” 一聲冷哼從身側(cè)響起于个,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暮顺,沒想到半個月后厅篓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡捶码,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年羽氮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惫恼。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡档押,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祈纯,到底是詐尸還是另有隱情令宿,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布盆繁,位于F島的核電站掀淘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏油昂。R本人自食惡果不足惜革娄,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一倾贰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拦惋,春花似錦匆浙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至言秸,卻和暖如春软能,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背举畸。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工查排, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抄沮。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓跋核,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叛买。 傳聞我的和親對象是個殘疾皇子砂代,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容