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ì)就可以看到一棵語法樹了柠横。
相關(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.
最終返回出去:
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ī)酱固。