ANTLR4(二) Vistor Listener
ANTLR4 Vistor Listener示例与对比
- Visitor Calculator
- 预期效果
- 语法文件
- 访问器模式生成语法分析器
- 重写Visitor
- 运行结果
- Listener Java
- 预期效果
- 语法文件
- 访问器模式生成语法分析器
- 重写Listener
- 运行结果
- Visitor 与 Listener
Visitor Calculator
我们将以访问者模式做一个计算器。
预期效果
输入
:
193
a=5
b=6
a+b*2
(1+2)*3
输出
:
193
17
9
PS:每次操作都需要换行
,输入’!'可以重置
标识符对应的数值。
语法文件
Visitor模式:通过在语法规则的每条分支后加上 # Identifier
(注意不能和规则名冲突)这样类似标签
的形式。
使得对于每种输入我们都有不同的处理方法,后续会介绍如何定义这些处理方法。
此外分享一下在设计clear也就是清零语法时的一些心得:
- 文中采用的是,输入
!
时,自动释放标识符与数值的所有对应,并且输出clear并且换行。但一开始考虑的是输入字符串clear
,清零后自动换行。放弃这种方法的理由是:clear字符串本身会先被expr规则
中的ID识别出来。如果要在ID的visit处理函数
中识别clear,又会因为返回值
必须是Integar类型而矛盾,最后放弃了输入clear这个方法。 - 要注意CLEAR后必须要跟NEWLINE,因为每一行都需要以回车或者换行结束操作。
//LabeledExpr.g4
grammar LabeledExpr; prog: stat+ ;stat: expr NEWLINE # printExpr| ID '=' expr NEWLINE # assign| CLEAR NEWLINE # clearMemory| NEWLINE # blank;expr: expr op=('*'|'/') expr # MulDiv| expr op=('+'|'-') expr # AddSub| INT # int| ID # id| '(' expr ')' # parens;CLEAR : '!' ;
MUL : '*' ;
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
访问器模式生成语法分析器
输入
:
antlr4 -no-listener -visitor LabeledExpr.g4
除了常见的几个文件以外,我们会生成以下两个文件:
LabeledExprVisitor、LabeledExprBaseVisitor
前者是分支处理函数的定义
,后者是分支处理函数的实现
。
我们可以重写LabeledExprBaseVistor,来自定义每个分支标签在输入情况匹配时
的动作。
重写Visitor
观察以下代码我们可以发现,Visitor生成的处理函数命名为 visitor+标签名
。
理论上,我们需要重写每个分支处理函数来应对不同的输入情况。
有一句格外的显眼:visit
(ctx.expr())。
我们不管括号内的expr(),实际上是左循环的token,为了获取子分支的情况,我们需要显式地
调用visit()。
以visitPrintExpr
为例,这句语法的目的是打印出表达式的结果。而表达式本身有多种情况,可以是INT、标识符、嵌套表达式,但是我们
不需要在这个分支中去操心这些,上文说过我们需要重写每个子分支的处理函数,因此在这里只需要通过visit得到expr的值
就可以了。
而在下面的visitInt、visitId等分支处理函数中,最后返回了处理过后的值
。
因此我们以这样一种由上而下的方式,满足了各种输入的情况。
//EvalVistor.java
import java.util.HashMap;
import java.util.Map;public class EvalVisitor extends LabeledExprBaseVisitor<Integer> {/** "memory" for our calculator; variable/value pairs go here */Map<String, Integer> memory = new HashMap<String, Integer>();/** ID '=' expr NEWLINE */@Overridepublic Integer visitAssign(LabeledExprParser.AssignContext ctx) {String id = ctx.ID().getText(); // id is left-hand side of '='int value = visit(ctx.expr()); // compute value of expression on rightmemory.put(id, value); // store it in our memoryreturn value;}/** expr NEWLINE */@Overridepublic Integer visitPrintExpr(LabeledExprParser.PrintExprContext ctx) {Integer value = visit(ctx.expr()); // evaluate the expr childSystem.out.println(value); // print the resultreturn 0; // return dummy value}/** CLEAR MEMORY */@Overridepublic Integer visitClearMemory(LabeledExprParser.ClearMemoryContext ctx) {memory.clear();System.out.println("clear");return 0;}/** INT */@Overridepublic Integer visitInt(LabeledExprParser.IntContext ctx) {return Integer.valueOf(ctx.INT().getText());}/** ID */@Overridepublic Integer visitId(LabeledExprParser.IdContext ctx) {String id = ctx.ID().getText();if ( memory.containsKey(id) ) return memory.get(id);return 0;}/** expr op=('*'|'/') expr */@Overridepublic Integer visitMulDiv(LabeledExprParser.MulDivContext ctx) {int left = visit(ctx.expr(0)); // get value of left subexpressionint right = visit(ctx.expr(1)); // get value of right subexpressionif ( ctx.op.getType() == LabeledExprParser.MUL ) return left * right;return left / right; // must be DIV}/** expr op=('+'|'-') expr */@Overridepublic Integer visitAddSub(LabeledExprParser.AddSubContext ctx) {int left = visit(ctx.expr(0)); // get value of left subexpressionint right = visit(ctx.expr(1)); // get value of right subexpressionif ( ctx.op.getType() == LabeledExprParser.ADD ) return left + right;return left - right; // must be SUB}/** '(' expr ')' */@Overridepublic Integer visitParens(LabeledExprParser.ParensContext ctx) {return visit(ctx.expr()); // return child expr's value}
}
运行结果
编写一个主函数去整合语法分析器以及自定义Vistor:
//Calc.java
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;import java.io.FileInputStream;
import java.io.InputStream;public class Calc {public static void main(String[] args) throws Exception {String inputFile = null;if ( args.length>0 ) inputFile = args[0];InputStream is = System.in;if ( inputFile!=null ) is = new FileInputStream(inputFile);ANTLRInputStream input = new ANTLRInputStream(is);LabeledExprLexer lexer = new LabeledExprLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);LabeledExprParser parser = new LabeledExprParser(tokens);ParseTree tree = parser.prog(); // parseEvalVisitor eval = new EvalVisitor();eval.visit(tree);}
}
待用的输入:
//t.expr
aa = 5
bb = 6
aa+bb*2
!
aa
bb
编译后,输入java Calc t.expr:
Listener Java
我们将以监听者模式抽取Java类中的方法,并整合成一个接口。
预期效果
输入
:
import java.util.List;
import java.util.Map;
public class Demo {void f(int x, String y) { }int[ ] g(/*no args*/) { return null; }List<Map<String, Integer>>[] h() { return null; }
}
输出
:
语法文件
整个Java.g4文件比较大,这里我们只列举出关于class、method、import的语法识别部分。
Listener模式:与Visitor模式不同的是,Listener无需手动地
在各个分支后打上标签,当我们以监听者模式生成语法分析器时,语法分析器
每遍历一个规则都会有两次响应事件
,分别是enter
以及exit
。
例如:classDeclaration,会生成enter
classDeclaration和exit
classDeclaration,我们可以在这两个时刻被监听到时响应
它们。
//Java.g4
classDeclaration: 'class' Identifier typeParameters? ('extends' type)?('implements' typeList)?classBody;
importDeclaration: 'import' 'static'? qualifiedName ('.' '*')? ';';
methodDeclaration: type Identifier formalParameters ('[' ']')* methodDeclarationRest| 'void' Identifier formalParameters methodDeclarationRest;
访问器模式生成语法分析器
输入
:
antlr4 Java.g4(缺省是listener模式)
除了常见的几个文件以外,我们会生成以下两个文件:
JavaListener.java JavaBaseListener.java
前者是遍历整个语法分析树的全部响应事件定义
,后者是它的实现
。
我们可以重写 JavaBaseListener,选择性地编写那些响应事件。
重写Listener
实际上我们只需要重写ImportDeclaration
的enter、ClassDeclaration
的enter和exit、MethodDeclaration
的enter即可。
因为我们预想的类定义需要开始和结尾的{和},因此需要在exit
时响应。而import和method对结尾并无要求。
以下代码中需要注意的是,有时需要用到parser.getTokenStream
去获取token,有时候直接可以通过ctx.
就可以获取。
比如:qualifiedName
需要通过parser.getTokenStream获取,而Identifier
通过ctx.就可以获取。
观察可以发现前者是语法规则的命名,而后者是词法规则的命名,可见前者这样的语法规则定义的词法符号
往往需要通过语法分析器遍历获取。
//ExtractInterfaceListener.java
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;public class ExtractInterfaceListener extends JavaBaseListener {JavaParser parser;public ExtractInterfaceListener(JavaParser parser) {this.parser = parser;}@Override public void enterImportDeclaration(JavaParser.ImportDeclarationContext ctx) {TokenStream tokens = parser.getTokenStream();String temp=tokens.getText(ctx.qualifiedName());System.out.println("import "+temp+';');}/** Listen to matches of classDeclaration */@Overridepublic void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx){System.out.println("interface I"+ctx.Identifier()+" {");}@Overridepublic void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx) {System.out.println("}");}/** Listen to matches of methodDeclaration */@Overridepublic void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx){// need parser to get tokensTokenStream tokens = parser.getTokenStream();String type = "void";if ( ctx.type()!=null ) {type = tokens.getText(ctx.type());}String args = tokens.getText(ctx.formalParameters());System.out.println("\t"+type+" "+ctx.Identifier()+args+";");}
}
运行结果
编写一个主函数去整合语法分析器以及自定义Listener:
//ExtractInterfaceTool.java
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.*;import java.io.FileInputStream;
import java.io.InputStream;public class ExtractInterfaceTool {public static void main(String[] args) throws Exception {String inputFile = null;if ( args.length>0 ) inputFile = args[0];InputStream is = System.in;if ( inputFile!=null ) {is = new FileInputStream(inputFile);}ANTLRInputStream input = new ANTLRInputStream(is);JavaLexer lexer = new JavaLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);JavaParser parser = new JavaParser(tokens);ParseTree tree = parser.compilationUnit(); // parseParseTreeWalker walker = new ParseTreeWalker(); // create standard walkerExtractInterfaceListener extractor = new ExtractInterfaceListener(parser);walker.walk(extractor, tree); // initiate walk of tree with listener}
}
待用的输入:
//Demo.java
import java.util.List;
import java.util.Map;
public class Demo {void f(int x, String y) { }int[ ] g(/*no args*/) { return null; }List<Map<String, Integer>>[] h() { return null; }
}
编译后,输入java ExtractInterfaceTool Demo.java:
Visitor 与 Listener
我们可以从主函数的角度去对比:
//VisitorEvalVisitor eval = new EvalVisitor();eval.visit(tree);
//ListenerParseTreeWalker walker = new ParseTreeWalker(); // create standard walkerExtractInterfaceListener extractor = new ExtractInterfaceListener(parser);walker.walk(extractor, tree); // initiate walk of tree with listener
在产生Visitor的语法分析器之前,我们需要对子分支打上标签
。产生语法分析器后,需要重写Visitor(每个子分支的visit
函数)。最后在主函数中,我们用重写的Visitor实例
来访问语法分析树。
在产生Listener的语法分析器之前,我们并不需要外的操作。产生语法分析器后,需要重写Listener(可选的每个子分支的enter及exit
函数)。最后在主函数中,我们新建一个语法树唤醒器
,再新建一个重写的Listener实例
,把语法分析树和Listener实例放到唤醒器
中。
对比以下可以发现:
- visitor对于子分支的处理是
主动
的(打标签),而listener是自动
的。 - visitor会将输入与子分支进行比对再触发响应,而listener会在enter、exit两个节点触发响应。
- 在对某个分支的处理时,如果需要调用其子分支,visitor需要主动地
visit
。而listener在遍历时会自动处理
,这让我们无需关注子分支的细节。
ANTLR4(二) Vistor Listener相关推荐
- Java Web基础入门第八十二讲 Listener(监听器)——监听器在开发中的应用(一)
监听器在JavaWeb开发中用得比较多,下面说一下监听器(Listener)在开发中的常见应用. 统计当前在线人数 在JavaWeb应用开发中,有时候我们需要统计当前在线的用户数,此时就可以使用监听器 ...
- java web 全局_JavaWeb - 【Listener】初始化全局资源
JavaWeb - [Listener]初始化全局资源 JavaWeb - [Listener]初始化全局资源 Listener使用步骤 Listener功能 初始化全局资源(读取配置文件) Serv ...
- 从定义到AST及其遍历方式,一文带你搞懂Antlr4
摘要:本文将首先介绍Antlr4 grammer的定义方式,如何通过Antlr4 grammer生成对应的AST,以及Antlr4 的两种AST遍历方式:Visitor方式和Listener方式. 1 ...
- Filter 过滤器和 Listener 监听器,java面试必问底层
一.Filter 过滤器 1.概述 2.开发步骤 3.过滤器执行流程 4.过滤器生命周期 5.过滤器配置问题 6.过滤器链(配置多个过滤器) [二.Listener 监听器]( <一线大厂Jav ...
- Listener(监听器)的简单介绍
Listener(监听器)的作用和内部机制 作用:监听某个事件的发生,状态的改变 内部机制:接口回调 八个web监听器 实现监听: 创建类实现监听器接口 web.xml文件中配置(注册)监听器< ...
- oracle tns和sid,oracle tns listener配置 (附TNS介绍)
一.tnsnames.ora 用途:(用于客户端)告诉oracle client应该从哪连.连到哪.TEST = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)( ...
- Oracle RAC万能集群测试大全 支持11g/12c/18c/19c版本
为了方便阅读,请横屏观看代码部分 一.Oracle RAC集群测试背景 某中大型制造业公司,由于要新上项目,建设了一套业务系统-ERP系统,这套系统的数据库环境是Oracle RAC(RHEL Lin ...
- 09_Filter过滤器(访问所有资源前,首先执行自定义过滤器类的doFilter方法)_Listener监听器(监听域对象的改变)
Filter 什么是Web过滤器? 如何使用Filter过滤器? 自定义Filter类的生命周期 过滤器可以拦截哪些资源路径? 拦截方式配置:资源被访问的方式 过滤器链的默认执行顺序是什么? 如何配置 ...
- FilterListener(超详细)
1.Filter ?什么是过滤器 Filter 过滤器它是 JavaWeb 的三大组件之一. 三大组件分别是:Servlet 程序.Listener 监听器.Filter 过滤器 Filter 过滤器 ...
最新文章
- 整数转换为罗马数字 Integer to Roman
- Red Hat Linux 挂载外部资源
- (转)各种纹理贴图技术
- YUV / RGB 格式及快速转换算法总结(转载)
- c语言两种加法,两个超长正整数的加法
- UIImagePickerController和UIAlertController结合使用
- docker centos 环境 安装 python
- 遵义大数据中心项目工程概况_市委书记张新文到曹州云都大数据中心等项目现场调研建设情况...
- JS代码优化工具Prepack
- npm 端口设置成80_13 个 NPM 快速开发技巧
- ACM弱校ACMer A HDU1045Fire Net有感
- 【转】各种字符串算法大总结
- [转载] python 列表List中index函数的坑
- 50个提高会话技巧的方法 (转IT经理人)
- hex2bin和bin2hex互转的小程序源代码
- caffe 安装教程(一)
- 平均股价的时间序列图形_统计学-时间序列分析ppt
- 家用wifi的配置和重置
- 主板后置音频接口图解_图解主板前置音频线接法(一)
- Android 国内阿里云镜像
热门文章
- upc 7834 送礼物
- 准备进入ReRAM速度!Crossbar发布SMIC芯片样品
- 怎样一键比较2个CAD图纸文件的不同呢?
- CSS使用小操作(隐藏滚动,实现三角行等...)
- 淘宝打标API,旺旺打标签接口文档
- java对中文首字母拼音排序
- RN实现仿余额宝余额数字翻滚动画特效
- 微信小程序开发者工具使用vant组件
- 加息靴子落地铁矿石继续反弹,甲醇认购大涨,苹果10-01大跳水2022.5.5
- ‘parent.relativePath‘ points at com.xxx instead of org.springframework.boot:spring-boot-starter的快速解决