快速上手ANTLR
回顾前文:
下面通过两个实例来快速上手ANTLR。
使用Listener转换数组
完整源码见:https://github.com/bytesfly/antlr-demo/tree/main/array-init/src/main/java/com/github/bytesfly/arr
效果如下图所示:

该程序能识别输入的整数数组并将其转化为JSON格式的字符串数组。
语法规则为ArrayInit.g4,如下:
/** Grammars always start with a grammar header. This grammar is called
* ArrayInit and must match the filename: ArrayInit.g4
*/
grammar ArrayInit;
@header {package com.github.bytesfly.arr.antlr;}
/** A rule called init that matches comma-separated values between {...}. */
init : '{' value (',' value)* '}' ; // must match at least one value
/** A value can be either a nested array/struct or a simple integer (INT) */
value : init
| INT
;
// parser rules start with lowercase letters, lexer rules with uppercase
INT : [0-9]+ ; // Define token INT as one or more digits
WS : [ \t\r\n]+ -> skip ; // Define whitespace rule, toss it out
我们自定义IntToStringListener.java,如下:
public class IntToStringListener extends ArrayInitBaseListener {
private final StringBuilder builder = new StringBuilder();
/**
* Translate { to [
*/
@Override
public void enterInit(ArrayInitParser.InitContext ctx) {
builder.append('[');
}
/**
* Translate } to ]
*/
@Override
public void exitInit(ArrayInitParser.InitContext ctx) {
// 去除value节点后的逗号
builder.setLength(builder.length() - 1);
builder.append(']');
if (ctx.parent != null) {
// 嵌套结构需要追加逗号,与enterValue的处理保持一致
builder.append(',');
}
}
/**
* Translate integers to strings with ""
*/
@Override
public void enterValue(ArrayInitParser.ValueContext ctx) {
TerminalNode node = ctx.INT();
if (node != null) {
builder.append("\"").append(node.getText()).append("\",");
}
}
public String getResult() {
return builder.toString();
}
}
最终的转换程序为Translate.java,如下:
public class Translate {
public static void main(String[] args) throws Exception {
// 从键盘输入
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String s = sc.nextLine();
// 从字符串读取输入数据
CharStream input = CharStreams.fromString(s);
// 新建一个词法分析器
ArrayInitLexer lexer = new ArrayInitLexer(input);
// 新建一个词法符号的缓冲区,用于存储词法分析器将生成的词法符号
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 新建一个语法分析器,处理词法符号缓冲区中的内容
ArrayInitParser parser = new ArrayInitParser(tokens);
// 针对init规则,开始语法分析
ParseTree tree = parser.init();
// 新建一个通用的、能够触发回调函数的语法分析树遍历器
ParseTreeWalker walker = new ParseTreeWalker();
// 创建我们自定义的监听器
IntToStringListener listener = new IntToStringListener();
// 遍历语法分析过程中生成的语法分析树,触发回调
walker.walk(listener, tree);
// 打印转换结果
System.out.println(listener.getResult());
}
}
}
监听器机制的优雅之处在于,不需要自己编写任何遍历语法分析树的代码。事实上,我们甚至都不知道ANTLR运行库是怎么遍历语法分析树、怎么调用我们的方法的。我们只知道,在语法规则对应的语句的开始和结束位置处,我们的监听器方法可以得到通知。
当然如果想知道ANTLR运行库是怎么遍历语法分析树并不困难,见org.antlr.v4.runtime.tree.ParseTreeWalker#walk():
public void walk(ParseTreeListener listener, ParseTree t) {
if ( t instanceof ErrorNode) {
listener.visitErrorNode((ErrorNode)t);
return;
}
else if ( t instanceof TerminalNode) {
listener.visitTerminal((TerminalNode)t);
return;
}
RuleNode r = (RuleNode)t;
enterRule(listener, r);
int n = r.getChildCount();
for (int i = 0; i<n; i++) {
walk(listener, r.getChild(i));
}
exitRule(listener, r);
}
这段代码也非常容易懂,其实就是常规树的遍历,遍历过程中反调自定义的IntToStringListener中的实现方法。
使用Visitor构建计算器
该程序能识别赋值语句并做加减乘除四则运算,效果大致如下。
输入:
100
a = 1
b = 2
a+b*2
((2+3)*(6+1)-5) / 3
输出:
100
5
10
语法规则为Expr.g4,如下:
grammar Expr;
@header {package com.github.bytesfly.calculator.antlr;}
/** The start rule; begin parsing here. */
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]+ ; // match identifiers
INT : [0-9]+ ; // match integers
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal)
WS : [ \t]+ -> skip ; // toss out whitespace
编写一个用于处理计算逻辑的访问器EvalVisitor.java,如下:
public class EvalVisitor extends ExprBaseVisitor<Integer> {
/**
* 存放变量名和变量值的对应关系
*/
private final Map<String, Integer> memory = new HashMap<>();
/**
* ID '=' expr NEWLINE # assign
*/
@Override
public Integer visitAssign(ExprParser.AssignContext ctx) {
// 获取变量名
String id = ctx.ID().getText();
// 计算表达式的值
Integer value = visit(ctx.expr());
// 暂存到map中
memory.put(id, value);
return value;
}
/**
* expr NEWLINE # printExpr
*/
@Override
public Integer visitPrintExpr(ExprParser.PrintExprContext ctx) {
// 计算表达式的值
Integer value = visit(ctx.expr());
// 打印
System.out.println(value);
return 0;
}
/**
* INT # int
*/
@Override
public Integer visitInt(ExprParser.IntContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
/**
* ID # id
*/
@Override
public Integer visitId(ExprParser.IdContext ctx) {
String id = ctx.ID().getText();
return memory.getOrDefault(id, 0);
}
/**
* expr op=('*'|'/') expr # MulDiv
*/
@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;
}
}
/**
* expr op=('+'|'-') expr # AddSub
*/
@Override
public Integer visitAddSub(ExprParser.AddSubContext ctx) {
// 计算左侧子表达式的值
Integer left = visit(ctx.expr(0));
// 计算右侧子表达式的值
Integer right = visit(ctx.expr(1));
// 根据不同的操作符做相应的运算
if (ctx.op.getType() == ExprParser.ADD) {
return left + right;
} else {
return left - right;
}
}
/**
* '(' expr ')' # parens
*/
@Override
public Integer visitParens(ExprParser.ParensContext ctx) {
// 返回子表达式的值
return visit(ctx.expr());
}
}
最终能识别输入表达式并做加减乘除四则运算的Calc.java,如下:
public class Calc {
public static void main(String[] args) throws Exception {
// 读取resources目录下example.expr表达式文件
String s = FileUtil.readUtf8String("example.expr");
// 从字符串读取输入数据
CharStream input = CharStreams.fromString(s);
// 新建一个词法分析器
ExprLexer lexer = new ExprLexer(input);
// 新建一个词法符号的缓冲区,用于存储词法分析器将生成的词法符号
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 新建一个语法分析器,处理词法符号缓冲区中的内容
ExprParser parser = new ExprParser(tokens);
// 针对prog规则,开始语法分析
ParseTree tree = parser.prog();
// 创建访问器对象
EvalVisitor eval = new EvalVisitor();
// 访问语法树
eval.visit(tree);
}
}
对整个调用过程疑惑的朋友,建议自行下载源码,打断点观察执行逻辑。
从上面的例子可以看出:使用ANTLR4语法文件独立于程序,具有编程语言中立性。
访问器机制也使得语言识别之外的工作在我们所熟悉的Java领域进行。在生成的所需的语法分析器之后,就不再需要同ANTLR语法标记打交道了。
快速上手ANTLR的更多相关文章
- 【Python五篇慢慢弹】快速上手学python
快速上手学python 作者:白宁超 2016年10月4日19:59:39 摘要:python语言俨然不算新技术,七八年前甚至更早已有很多人研习,只是没有现在流行罢了.之所以当下如此盛行,我想肯定是多 ...
- 快速上手Unity原生Json库
现在新版的Unity(印象中是从5.3开始)已经提供了原生的Json库,以前一直使用LitJson,研究了一下Unity用的JsonUtility工具类的使用,发现使用还挺方便的,所以打算把项目中的J ...
- [译]:Xamarin.Android开发入门——Hello,Android Multiscreen快速上手
原文链接:Hello, Android Multiscreen Quickstart. 译文链接:Hello,Android Multiscreen快速上手 本部分介绍利用Xamarin.Androi ...
- [译]:Xamarin.Android开发入门——Hello,Android快速上手
返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...
- 快速上手seajs——简单易用Seajs
快速上手seajs——简单易用Seajs 原文 http://www.cnblogs.com/xjchenhao/p/4021775.html 主题 SeaJS 简易手册 http://yslo ...
- Git版本控制Windows版快速上手
说到版本控制,之前用过VSS,SVN,Git接触不久,感觉用着还行.写篇博文给大家分享一下使用Git的小经验,让大家对Git快速上手. 说白了Git就是一个控制版本的工具,其实没想象中的那么复杂,咱在 ...
- Objective-C快速上手
最近在开发iOS程序,这篇博文的内容是刚学习Objective-C时做的笔记,力图达到用最短的时间了解OC并使用OC.Objective-C是OS X 和 iOS平台上面的主要编程语言,它是C语言的超 ...
- Netron开发快速上手(二):Netron序列化
Netron是一个C#开源图形库,可以帮助开发人员开发出类似Visio的作图软件.本文继前文”Netron开发快速上手(一)“讨论如何利用Netron里的序列化功能快速保存自己开发的图形对象. 一个用 ...
- Netron开发快速上手(一):GraphControl,Shape,Connector和Connection
版权所有,引用请注明出处:<<http://www.cnblogs.com/dragon/p/5203663.html >> 本文所用示例下载FlowChart.zip 一个用 ...
随机推荐
- FZU ICPC 2020 寒假训练 4 —— 模拟(二)
P1056 排座椅 题目描述 上课的时候总会有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情.不过,班主任小雪发现了一些有趣的现象,当同学们的座次确定下来之后,只有有限的 D 对同 ...
- 大爽Python入门教程 1-2 数与字符串
大爽Python入门公开课教案 点击查看教程总目录 1 整数与浮点数 整数大家都知道,比如1, 2, 10, 123, 都是整数int. 浮点数是什么呢? 上一节的除法运算,不知道有没有人注意到,其结 ...
- Alpine容器安装运行ssh
写在前面 本文介绍了在Alpine容器(docker)上安装运行ssh并保证外界(宿主机)能通过ssh登录的方法,给出了相应的命令.在下在探索过程中借鉴了许多前人的经验,在此先行谢过,所有参考内容都会 ...
- [atAGC051D]C4
考虑将两次移动作为一个整体,两次移动的效果分为:$s-u$.$u-s$和原地不动 对于从$s$回到$s$路径,必然有前两种效果使用次数相同,假设都为$i$(枚举),那么原地不动的次数$j=\frac{ ...
- [nowcoder5669H]Harder Gcd Problem
题目相当于问1-n中最多能选出多少对不互素无交集的二元组,并要求方案 构造:将所有数放入其最小质因子对应的集合,若素数p所对应的集合元素个数为奇数且$p\ne 2$且$2p\le n$,那么就将$2p ...
- RestSharp使用说明
翻译自:https://github.com/restsharp/RestSharp/wiki,转载请注明. 一.新手入门 如果只有少量一次性请求需要封装为API,则可以如下使用RestSharp : ...
- 力扣 - 剑指 Offer 55 - I. 二叉树的深度
题目 剑指 Offer 55 - I. 二叉树的深度 思路1(DFS) 后续遍历吧,先遍历到最深(递归到末尾返回0),然后从后面一步一步比较取大的值返回,每次返回层数都加1, 执行流程是怎样的:比如其 ...
- docker版本演变,安装,基本命令
1.docker 版本信息 Docker CE在17.03版本之前叫Docker Engine,版本号从0.1.0(2013-03-23)~1.13.1(2017-02-08),详见https://d ...
- 3D-DNA 挂载染色体
3D-DNA是一款简单,方便的处理Hi-C软件,可将contig提升到染色体水平.其githup网址:https://github.com/theaidenlab/3d-dna 3D-DNA流程简介 ...
- [R] venn.diagram保存pdf格式文件?
vennDiagram包中的主函数绘图时,好像不直接支持PDF格式文件: dat = list(a = group_out[[1]][,1],b = group_out[[2]][,1]) names ...