[轉(zhuǎn)]利用ANTLR4實(shí)現(xiàn)一個(gè)簡(jiǎn)單的四則運(yùn)算計(jì)算器

ANTLR4介紹

ANTLR能夠自動(dòng)地幫助你完成詞法分析和語法分析的工作, 免去了手寫去寫詞法分析器和語法分析器的麻煩

它是基于LL(k)的, 以遞歸下降的方式進(jìn)行工作.ANTLR v4還支持多種目標(biāo)語言赌厅。本文用java來寫代碼微渠。

總結(jié)一下:ANTRL能自動(dòng)完成語法分析和詞法分析過程冀值,并生產(chǎn)框架代碼缸托,讓我們寫相關(guān)過程的時(shí)候只需要往固定位置添加代碼即可。大大簡(jiǎn)便了語法分析詞法分析的過程顿锰。

ANTLR4安裝配置

因?yàn)橛肐DEA,所以直接介紹在IDEA中怎么安裝,在IDEA中安裝ANTLR4相關(guān)插件即可胸梆。然后MAVEN引用下

<dependency>
        <groupId>org.antlr</groupId>
        <artifactId>antlr4</artifactId>
        <version>4.5.2</version>
</dependency>

ANTLR4 語法描述文件
ANTLR4有專門的語法來構(gòu)建整個(gè)過程

grammar Expr;

prog : stat+;

stat: expr NEWLINE          # printExpr
    | ID '=' expr NEWLINE   # assign
    | NEWLINE               # blank
    ;

expr: expr op=('*'|'/') expr    # MulDiv
| expr op=('+'|'-') expr        # AddSub
| INT                           # int
| ID                            # id
| '(' expr ')'                  # parens
;

MUL : '*' ; // assigns token name to '*' used above in grammar
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ;
INT : [0-9]+ ;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip;

相關(guān)語法很簡(jiǎn)單, 整體來說一個(gè)原則须板,遞歸下降碰镜。 即定義一個(gè)表達(dá)式(如expr),可以循環(huán)調(diào)用直接也可以調(diào)用其他表達(dá)式习瑰,但是最終肯定會(huì)有一個(gè)最核心的表達(dá)式不能再繼續(xù)往下調(diào)用了绪颖。

以上代碼在真正執(zhí)行的時(shí)候會(huì)生成一棵抽象語法樹,選擇“prog”然后->"Test Rule prog", 輸入測(cè)試數(shù)據(jù)“(1 + 2)+3-4*5”甜奄,然后我們會(huì)就可以看到一棵語法樹了柠横。

TB2XXo7XHBmpuFjSZFuXXaG_XXa_!!46754672.png

相關(guān)生成的java代碼

整個(gè)語法文件的目的是為了讓antlr生產(chǎn)相關(guān)的java代碼窃款。 我們先設(shè)置下生成visitor, 然后,他會(huì)生成如下幾個(gè)文件:

ExprParser
ExprLexer
ExprBaseVistor
ExprVisitor
ExprLexer 是詞法分析器牍氛, ExprParser是語法分析器晨继。 一個(gè)語言的解析過程一般過程是 詞法分析-->語法分析。這是ANTLR4為我們生成的框架代碼搬俊, 而我們唯一要做的是自己實(shí)現(xiàn)一個(gè)Vistor紊扬,一般從ExprBaseVistor繼承即可。

ANTLR 會(huì)為ExprBaseVistor 從定義的symoble文件如“#printExpr悠抹, #assign” 珠月,自動(dòng)生成相應(yīng)的還是,然后就實(shí)現(xiàn)這些還是就可以實(shí)現(xiàn)我們的功能了楔敌。 如:

@Override
public Integer visitAssign(ExprParser.AssignContext ctx) {
    String id = ctx.ID().getText();
    Integer value = visit(ctx.expr());
    this.memory.put(id, value);
    return value;

}

@Override
public Integer visitInt(ExprParser.IntContext ctx) {
    return Integer.valueOf(ctx.INT().getText());
}

@Override
public Integer visitMulDiv(ExprParser.MulDivContext ctx) {
    Integer left = visit(ctx.expr(0));
    Integer right = visit(ctx.expr(1));

    if (ctx.op.getType() == ExprParser.MUL){
        return left * right;
    }else{
        return left / right;
    }

}

解釋下Context的應(yīng)用啤挎, Context 可以通過 expr(i) 取上下文的子內(nèi)容。

然后就可以用如下方式是使用了:

    public static void main(String [] args) throws IOException {
        ANTLRInputStream inputStream = new ANTLRInputStream("1 + 2 + 3 * 4+ 6 / 2");
        ExprLexer lexer = new ExprLexer(inputStream);

        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
        ExprParser parser = new ExprParser(tokenStream);
        ParseTree parseTree = parser.prog();
        EvalVisitor visitor = new EvalVisitor();
        Integer rtn = visitor.visit(parseTree);
        System.out.println("#result#"+rtn.toString());
    }

運(yùn)行一下卵凑,可以得到正確的結(jié)果了庆聘。

分析下整個(gè)過程

好神奇。 我們來分析下整個(gè)過程是如何實(shí)現(xiàn)的勺卢。

首先Antlr4會(huì)根據(jù)相關(guān)的語法文件生成ExprParser類伙判,其內(nèi)容是由 其語法內(nèi)容決定的。如上的語法中有三個(gè)表達(dá)式:prog黑忱,stat宴抚,expr,所以就生成了三個(gè)函數(shù):

public final ProgContext prog() throws RecognitionException {
    ProgContext _localctx = new ProgContext(_ctx, getState());
    enterRule(_localctx, 0, RULE_prog);
    try {
        enterOuterAlt(_localctx, 1);
        {
        setState(6);
        stat();
        }
    }
    catch (RecognitionException re) {
        _localctx.exception = re;
        _errHandler.reportError(this, re);
        _errHandler.recover(this, re);
    }
    finally {
        exitRule();
    }
    return _localctx;
}
public final StatContext stat() throws RecognitionException {
    StatContext _localctx = new StatContext(_ctx, getState());
    enterRule(_localctx, 2, RULE_stat);
    ...

stat過程是真正的語法分析過程甫煞, 他會(huì)把相應(yīng)的token填上不同的StatContext.

整個(gè)語法解析的過程就是 prop -> stat ->expr菇曲。

在語法文件中有MUL,DIV 等幾個(gè)關(guān)鍵字抚吠, Antlr會(huì)自動(dòng)識(shí)別其是否有子項(xiàng)調(diào)用如果沒有則這樣定義:

public static final int
    T__0=1, T__1=2, T__2=3, MUL=4, DIV=5, ADD=6, SUB=7, ID=8, INT=9, NEWLINE=10, 
    WS=11;
public static final int
    RULE_prog = 0, RULE_stat = 1, RULE_expr = 2;

有了parser常潮, 下一個(gè)疑問就是parser如何和我們寫的visitor聯(lián)系起來的。 這就要借助于一個(gè)非常重要的概念:Context.
因?yàn)檎Z法文件中有8個(gè)symbol 楷力,所以會(huì)對(duì)于生成不同的Context.

TB2T9peX9VmpuFjSZFFXXcZApXa_!!46754672.png

最終返回出去:

ParseTree parseTree = parser.prog();
EvalVisitor visitor = new EvalVisitor();
Integer rtn = visitor.visit(parseTree);

一個(gè)典型的Context是這樣實(shí)現(xiàn)的:

public static class IntContext extends ExprContext {
    public TerminalNode INT() { return getToken(ExprParser.INT, 0); }
    public IntContext(ExprContext ctx) { copyFrom(ctx); }
    @Override
    public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
        if ( visitor instanceof ExprVisitor ) return ((ExprVisitor<? extends T>)visitor).visitInt(this);
        else return visitor.visitChildren(this);
    }
}

特別關(guān)注 accept 的實(shí)現(xiàn)喊式。

看下 visitor的實(shí)現(xiàn)

public T visit(ParseTree tree) {
    return tree.accept(this);
}

典型的visitor模式的實(shí)現(xiàn)。 以上這個(gè)流程是:

通過parser返回一個(gè)xxContext的樹
在visitor中調(diào)用 xxContent的accept方法
xxContext 調(diào)用visitor的具體實(shí)現(xiàn)方法: 如:visitMulDiv
在實(shí)現(xiàn)vistor方法時(shí)候萧朝,注意如果還有chilContent岔留,繼續(xù)往下。
總結(jié)
Antlr4 屏蔽了語法分析和詞法分析的細(xì)節(jié)检柬。大大簡(jiǎn)化了開發(fā)的工作量贸诚。 而且使用簡(jiǎn)單方便。對(duì)比 Boost.Spirit,簡(jiǎn)直一個(gè)是自動(dòng)擋的汽車厕吉,一個(gè)是飛機(jī)酱固。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市头朱,隨后出現(xiàn)的幾起案子运悲,更是在濱河造成了極大的恐慌,老刑警劉巖项钮,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件班眯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烁巫,警方通過查閱死者的電腦和手機(jī)署隘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亚隙,“玉大人磁餐,你說我怎么就攤上這事“⑵” “怎么了诊霹?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)渣淳。 經(jīng)常有香客問我脾还,道長(zhǎng),這世上最難降的妖魔是什么入愧? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任鄙漏,我火速辦了婚禮,結(jié)果婚禮上棺蛛,老公的妹妹穿的比我還像新娘怔蚌。我一直安慰自己,他們只是感情好鞠值,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布媚创。 她就那樣靜靜地躺著,像睡著了一般彤恶。 火紅的嫁衣襯著肌膚如雪钞钙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天声离,我揣著相機(jī)與錄音芒炼,去河邊找鬼。 笑死术徊,一個(gè)胖子當(dāng)著我的面吹牛本刽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼子寓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼暗挑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斜友,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤炸裆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鲜屏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烹看,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年洛史,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惯殊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胰苏,到底是詐尸還是另有隱情,我是刑警寧澤浪漠,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站霎褐,受9級(jí)特大地震影響址愿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冻璃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一响谓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧省艳,春花似錦娘纷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辐烂,卻和暖如春遏插,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纠修。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工胳嘲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扣草。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓了牛,卻偏偏與公主長(zhǎng)得像颜屠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹰祸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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