[轉]利用ANTLR4實現一個簡單的四則運算計算器

ANTLR4介紹

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

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

總結一下:ANTRL能自動完成語法分析和詞法分析過程,并生產框架代碼,讓我們寫相關過程的時候只需要往固定位置添加代碼即可。大大簡便了語法分析詞法分析的過程。

ANTLR4安裝配置

因為用IDEA,所以直接介紹在IDEA中怎么安裝,在IDEA中安裝ANTLR4相關插件即可。然后MAVEN引用下

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

ANTLR4 語法描述文件
ANTLR4有專門的語法來構建整個過程

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;

相關語法很簡單, 整體來說一個原則,遞歸下降。 即定義一個表達式(如expr),可以循環調用直接也可以調用其他表達式,但是最終肯定會有一個最核心的表達式不能再繼續往下調用了。

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

TB2XXo7XHBmpuFjSZFuXXaG_XXa_!!46754672.png

相關生成的java代碼

整個語法文件的目的是為了讓antlr生產相關的java代碼。 我們先設置下生成visitor, 然后,他會生成如下幾個文件:

ExprParser
ExprLexer
ExprBaseVistor
ExprVisitor
ExprLexer 是詞法分析器, ExprParser是語法分析器。 一個語言的解析過程一般過程是 詞法分析-->語法分析。這是ANTLR4為我們生成的框架代碼, 而我們唯一要做的是自己實現一個Vistor,一般從ExprBaseVistor繼承即可。

ANTLR 會為ExprBaseVistor 從定義的symoble文件如“#printExpr, #assign” ,自動生成相應的還是,然后就實現這些還是就可以實現我們的功能了。 如:

@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的應用, Context 可以通過 expr(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());
    }

運行一下,可以得到正確的結果了。

分析下整個過程

好神奇。 我們來分析下整個過程是如何實現的。

首先Antlr4會根據相關的語法文件生成ExprParser類,其內容是由 其語法內容決定的。如上的語法中有三個表達式:prog,stat,expr,所以就生成了三個函數:

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過程是真正的語法分析過程, 他會把相應的token填上不同的StatContext.

整個語法解析的過程就是 prop -> stat ->expr。

在語法文件中有MUL,DIV 等幾個關鍵字, Antlr會自動識別其是否有子項調用如果沒有則這樣定義:

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, 下一個疑問就是parser如何和我們寫的visitor聯系起來的。 這就要借助于一個非常重要的概念:Context.
因為語法文件中有8個symbol ,所以會對于生成不同的Context.

TB2T9peX9VmpuFjSZFFXXcZApXa_!!46754672.png

最終返回出去:

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

一個典型的Context是這樣實現的:

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);
    }
}

特別關注 accept 的實現。

看下 visitor的實現

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

典型的visitor模式的實現。 以上這個流程是:

通過parser返回一個xxContext的樹
在visitor中調用 xxContent的accept方法
xxContext 調用visitor的具體實現方法: 如:visitMulDiv
在實現vistor方法時候,注意如果還有chilContent,繼續往下。
總結
Antlr4 屏蔽了語法分析和詞法分析的細節。大大簡化了開發的工作量。 而且使用簡單方便。對比 Boost.Spirit,簡直一個是自動擋的汽車,一個是飛機。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容