ANTLR4-实践
·
最近在研究Yang模型转换,自然而然就看到了ANTLR4与Xtend的相关技术。简单学习下基本用法,以此作为记录。
对于ANTLR4是一款优秀的开源语法解析器。不仅如此,可以为开发者自己定义的语法提供识别解析计算的功能。从一个简单的小例子(仅作测试)开始:
如定义一个语法:
数字 操作A操作B操作C操作D 数据
具象一点
10 +* 2
计算结果是 (10+2)*2=24
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.5.3</version>
</dependency>
利用ANTLR开发的一般步骤:
1.定义文法
grammar Jia;
expres : expres (PLUS|SUB|MUL|DIV)* NUMBER| NUMBER;
NUMBER : [0]|[1-9]+[0-9]*;
PLUS : 'p';
SUB : 's' ;
MUL : 'm' ;
DIV : 'd' ;
WS : [ \t\n\r]+ -> skip ;
具体规则含义参考百度,
() : 产生式组合
? : 产生式出现0或1次
* : 0或多次
+ : 1或多次
. : 任意一个字符
~ : 不出现后面的字符
.. : 字符范围
从上文的语法定义中,已经能够识别出如1 p 2 或者 1 md 2 之类的表达式
intelligent 的ANTLR插件提供了对语言的语法进行校验功能,所见即所得,在文法复杂时,则非常实用。
合法输入:
非法输入:
2.根据g4文件生成相应的antlr辅助代码
右击文法文件,输入生成文件所在目录(需要确保该目录在classpath下)
注意ANTLR生成语法树有两种遍历方法,分别为listener与visitor,如上默认选择的是listener.
右击生成
ps.
listener与vistor两个语法树遍历机制的区别:
listener:在遍历语法树时,自动回调Listener中回调方法。实现简单,全自动,只需要实现listener中的各个接口方法。即可完成语义表达。并采用深度优先遍历
expres-->expres->expres->10->p->s->m->29->p->2
vistior:则提供了可以控制遍历过程的一种方式,通过显示调用visit方法,可以访问子树节点。
3.自定义listener执行语义分析及表达式计算
package com.sunquan.demo.antlr.listener;
import com.sunquan.jia.antlr.gen.JiaBaseListener;
import com.sunquan.jia.antlr.gen.JiaParser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Created by on 2017/5/10.
*/
public class JiaListener extends JiaBaseListener {
private Stack<Integer> numStack = new Stack<>();
private Queue<String> symbolQueue = new ConcurrentLinkedQueue<>();
@Override
public void enterExpres(JiaParser.ExpresContext ctx) {
super.enterExpres(ctx);
}
@Override
public void enterEveryRule(ParserRuleContext ctx) {
super.enterEveryRule(ctx);
}
@Override
public void visitTerminal(TerminalNode node) {
super.visitTerminal(node);
String text = node.getText();
if (isCalSymbol(text))
symbolQueue.add(text);
else
cal(text);
}
private boolean isCalSymbol(String text) {
return text.equals("p") || text.equals("s") || text.equals("m") || text.equals("d");
}
private void cal(String text) {
if (text != null && !text.equals(" ")) {
if (symbolQueue.isEmpty()) {
numStack.push(Integer.parseInt(text));
} else {
while (!symbolQueue.isEmpty()) {
String symbol = symbolQueue.poll();
switch (symbol) {
case "p":
numStack.push(numStack.pop() + Integer.parseInt(text));
break;
case "s":
numStack.push(numStack.pop() - Integer.parseInt(text));
break;
case "m":
numStack.push(numStack.pop() * Integer.parseInt(text));
break;
case "l":
numStack.push(numStack.pop() / Integer.parseInt(text));
break;
default:
throw new RuntimeException("not support symbol");
}
}
}
}
}
@Override
public void visitErrorNode(ErrorNode node) {
super.visitErrorNode(node);
}
public int result() {
return numStack.pop();
}
}
4测试结果
@Test
public void test3() {
// PLUS : 'p';
// SUB : 's' ;
// MUL : 'm' ;
// DIV : 'd' ;
String hello = "1 psw 2";
ANTLRInputStream inputStream = new ANTLRInputStream(hello);
JiaLexer lexer = new JiaLexer(inputStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
JiaParser parser = new JiaParser(tokenStream);
ParseTreeWalker walker = new ParseTreeWalker();
JiaListener jiaListener = new JiaListener();
walker.walk(jiaListener, parser.expres());
System.out.println(jiaListener.result());
}
ps. 为方便visitor与listener的比较,针对visitor的遍历计算如下:
package com.sunquan.demo.antlr.listener;
import com.sunquan.jiav.antlr.gen.JiavBaseVisitor;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Created by on 2017/5/10.
*/
public class JiavVisitor extends JiavBaseVisitor<Integer> {
private Stack<Integer> numStack = new Stack<>();
private Queue<String> symbolQueue = new ConcurrentLinkedQueue<>();
@Override
public Integer visitTerminal(TerminalNode node) {
String text = node.getText();
if (isCalSymbol(text))
symbolQueue.add(text);
else
cal(text);
return numStack.peek();
}
private boolean isCalSymbol(String text) {
return text.equals("p") || text.equals("s") || text.equals("m") || text.equals("d");
}
private void cal(String text) {
if (text != null && !text.equals(" ")) {
if (symbolQueue.isEmpty()) {
numStack.push(Integer.parseInt(text));
} else {
while (!symbolQueue.isEmpty()) {
String symbol = symbolQueue.poll();
switch (symbol) {
case "p":
numStack.push(numStack.pop() + Integer.parseInt(text));
break;
case "s":
numStack.push(numStack.pop() - Integer.parseInt(text));
break;
case "m":
numStack.push(numStack.pop() * Integer.parseInt(text));
break;
case "l":
numStack.push(numStack.pop() / Integer.parseInt(text));
break;
default:
throw new RuntimeException("not support symbol");
}
}
}
}
}
}
@Test
public void test4() {
// PLUS : 'p';
// SUB : 's' ;
// MUL : 'm' ;
// DIV : 'd' ;
String hello = "1 pm 3";
ANTLRInputStream inputStream = new ANTLRInputStream(hello);
JiavLexer lexer = new JiavLexer(inputStream);
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
JiavParser parser = new JiavParser(tokenStream);
JiavVisitor visitor = new JiavVisitor();
Integer result = visitor.visit(parser.expres());
System.out.println(result);
}
万事开头难,入了门就好多了。
更多推荐
已为社区贡献1条内容
所有评论(0)