当前位置: 首页 > backend >正文

Antlr学习笔记 02、使用antlr4实现简易版计算器

文章目录

  • 前言
  • 源码路径
  • 实现目标
  • 详细实现步骤
    • 1、引入antlr4的pom依赖与插件
    • 2、编写语法规则文件:Calculator.g4
    • 3、自动生成词法、语法解析器
      • 配置Antlr生成参数
      • 命令生成词法、语法解析器
    • 4、实现自定义visitor来计算表达式值
    • 5、实现测试方法(自定义visitor中)
  • 参考资料
  • 资料获取

antlr4实现简易版计算器

前言

博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

CSDN搜索:长路

视频平台:b站-Coder长路

源码路径

img

当前文档配套相关源码地址:

  • gitee:https://gitee.com/changluJava/demo-exer/tree/master/java-sqlparser/demo-Antlr/demo-Antlr4-calculator
  • github:https://github.com/changluya/Java-Demos/tree/master/java-sqlparser/demo-Antlr/demo-Antlr4-calculator

实现目标

支持两种情况:

表达式 换行
变量 = 表达式

对两种情况表达式来进行计算

举例:

a=1
b=2
c=a+b*2
d=3+c
c
d

不仅仅还要实现表达式的计算,在这里额外多出来针对变量c、d最终值的计算处理。

期望输出的是c、d的取值:

img

详细实现步骤

1、引入antlr4的pom依赖与插件

<properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><antlr.version>4.8</antlr.version>
</properties><dependencies><dependency><groupId>org.antlr</groupId><artifactId>antlr4-runtime</artifactId><version>${antlr.version}</version></dependency>
</dependencies><build><plugins><plugin><groupId>org.antlr</groupId><artifactId>antlr4-maven-plugin</artifactId><version>${antlr.version}</version><executions><execution><id>antlr</id><goals><goal>antlr4</goal></goals><phase>generate-sources</phase></execution></executions><configuration><sourceDirectory>${basedir}/src/main/resources</sourceDirectory><outputDirectory>${basedir}/src/main/java/com/changlu/parser</outputDirectory><listener>true</listener><visitor>true</visitor><treatWarningsAsErrors>true</treatWarningsAsErrors></configuration></plugin></plugins>
</build>

对于该插件后续可执行运行命令:

mvn antlr4:antlr4

2、编写语法规则文件:Calculator.g4

img

// 带赋值的计算器的语法定义文件
grammar Calculator; // 语法的名字@header {package com.changlu.parser;
}// EBNF语法表示法
// 程序program是由一系列语句构成的
// +表示1个或多个
program: statement+;// 语句: 三种描述方式
//   - 表达式 换行符
//   - 赋值语句:标识符 '=' 换行符
//   - 换行符
// 注意:#在这里并不是注释的意思,在antlr中 # 可以给语法规则取名字
statement: expression NEWLINE           # printExpression| ID '=' expression NEWLINE    # assign| NEWLINE                     # blank;// 编写表达式
// INT表示整型、ID表示变量名(自定义的)
// op是我们给运算符取的名字
expression : expression op=('*'|'/') expression  # MulDiv| expression op=('+'|'-') expression  # AddSub| INT                                 # int| ID                                  # id| '(' expression ')'                  # parens;// 变量名:一个或者多个大小写字母
ID : [a-zA-Z]+;
// 整型
INT : [0-9]+;
// 换行符 \r?表示0个或者1个回车符
NEWLINE : '\r'? '\n';
// 空白字符
// [ \t]+ 含义如下:
//      [ ]:表示字符集合,匹配方括号内的任意一个字符。
//      (空格):表示空格字符。
//       \t:表示制表符(Tab 键产生的字符)。
//      [ \t]:表示匹配一个空格字符或一个制表符。
//      +:表示前面的字符集合 [ \t] 可以出现一次或多次。
// -> skip 是 ANTLR 的一个指令,表示当解析器遇到匹配此规则的输入时,会跳过这些字符,不将它们作为 Token 传递给语法分析器。
WS : [ \t]+ -> skip;//实际上面expression的语法规则中,可以使用MUL、DIV、ADD、SUB 代替,但是为了简洁,可以不使用
MUL: '*';
DIV: '/';
ADD: '+';
SUB: '-';

实现直接Test测试语法规则:

img

对于写多行(带有换行符),也是可以解析到多个statement情况

img

针对目前测试的场景表达式求值:

a=1
b=2
c=a+b*2
d=3+c
c
d

img


3、自动生成词法、语法解析器

配置Antlr生成参数

img

img

支持生成监听者模式以及访问者模式,我们一般选择访问者模式生成。

命令生成词法、语法解析器

执行命令:

img

mvn antlr4:antlr4

此时会在步骤1中指定的目录下生成相关词法、语法文件。

有用的只是这六个文件:

img

lexer:词法分析器。用于切分为词法标记流。

Parser:语法分析器。进行语法分析,将其转换为抽象语法树。

说明:目前对于监听器Listener暂无使用场景后续可都删除掉。


4、实现自定义visitor来计算表达式值

对于如何计算表达式值,可以通过遍历语法树来进行计算。

img

说明:这里由于是要求表达式的计算值,所以这里extends CalculatorBaseVisitor中泛型为Integer,表示访问某个节点值最终返回的都是数值。

package com.changlu;import com.changlu.parser.CalculatorBaseVisitor;
import com.changlu.parser.CalculatorLexer;
import com.changlu.parser.CalculatorParser;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;import java.util.HashMap;
import java.util.Map;// 计算器的结果是整型,所以访问抽象语法树的每个节点的返回值也是整型
public class CalculatorVisitor extends CalculatorBaseVisitor<Integer> {// map 用来保存变量和值的变量关系  a = 1  => {"a": 1}// key:变量名// value:变量名的所对应的值Map<String, Integer> memory = new HashMap<>();/*** 访问变量名 a = expression* @param ctx the parse tree* @return 返回表达式的值*/@Overridepublic Integer visitAssign(CalculatorParser.AssignContext ctx) {// 取出=左边的变量名String paramName = ctx.ID().getText();// 取出=右边的表达式// 根据表达式来求值Integer value = visit(ctx.expression());memory.put(paramName, value);return value;}// expression NEWLINE@Overridepublic Integer visitPrintExpression(CalculatorParser.PrintExpressionContext ctx) {Integer value = visit(ctx.expression());// 每针对带有 expression NEWLINE 情况会进行打印System.out.println(ctx.expression().getText() + " = " + value);return value;}// 访问到INT表达式情况@Overridepublic Integer visitInt(CalculatorParser.IntContext ctx) {return Integer.parseInt(ctx.INT().getText());}// 访问到 '(' expression ')'情况@Overridepublic Integer visitParens(CalculatorParser.ParensContext ctx) {// 直接返回'(' expression ')' 中的表达式的值return visit(ctx.expression());}// 访问到 expression op=('*'|'/') expression 情况@Overridepublic Integer visitMulDiv(CalculatorParser.MulDivContext ctx) {Integer leftVal = visit(ctx.expression(0));Integer rightVal = visit(ctx.expression(1));if (ctx.op.getType() == CalculatorParser.MUL) {return leftVal * rightVal;}else {return leftVal / rightVal;}}// 访问到 expression op=('+'|'-') expression 情况@Overridepublic Integer visitAddSub(CalculatorParser.AddSubContext ctx) {Integer leftVal = visit(ctx.expression(0));Integer rightVal = visit(ctx.expression(1));if (ctx.op.getType() == CalculatorParser.ADD) {return leftVal + rightVal;}else {return leftVal - rightVal;}}// 访问到 ID 情况 (最终得到求表达式的值)@Overridepublic Integer visitId(CalculatorParser.IdContext ctx) {String id = ctx.ID().getText();if (memory.containsKey(id)) return memory.get(id);return 0;}}

对于某个节点,可根据语法文件.g4来进行编写访问过程操作:

img

5、实现测试方法(自定义visitor中)

img

public static void main(String[] args) {String expr = "a = 1\n" +"b = 2\n" +"c = a + b * 2\n" +"d = 3 + c\n" +"c\n" +"d\n";// 1、将字符串转换成流CodePointCharStream stream = CharStreams.fromString(expr);// 2、实例化词法分析器 进行词法分析// 词法分析器初始化CalculatorLexer lexer = new CalculatorLexer(stream);// 词法分析CommonTokenStream tokens = new CommonTokenStream(lexer);// 3、实例化语法分析器CalculatorParser parser = new CalculatorParser(tokens);// 语法分析并转为抽象语法树CalculatorParser.ProgramContext tree = parser.program();// 进行表达式求值CalculatorVisitor visitor = new CalculatorVisitor();visitor.visit(tree);
}

运行测试方法:

img

参考资料

[1]. 尚硅谷技术中台实战教程,大数据九章云台项目【视频 antlr】:https://www.bilibili.com/video/BV1vR4y1z79G

资料获取

大家点赞、收藏、关注、评论啦~

精彩专栏推荐订阅:在下方专栏👇🏻

  • 长路-文章目录汇总(算法、后端Java、前端、运维技术导航):博主所有博客导航索引汇总
  • 开源项目Studio-Vue—校园工作室管理系统(含前后台,SpringBoot+Vue):博主个人独立项目,包含详细部署上线视频,已开源
  • 学习与生活-专栏:可以了解博主的学习历程
  • 算法专栏:算法收录

更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅

http://www.xdnf.cn/news/16972.html

相关文章:

  • 【无标题】标准 I/O 中的一些函数,按功能分类说明其用法和特点
  • 【LeetCode刷题集】--排序(一)
  • clocking_cb驱动之坑
  • BackgroundTasks 如何巧妙驾驭多任务并发?
  • 测试-概念篇(3)
  • <PhotoShop><JavaScript><脚本>基于JavaScript,利用脚本实现PS软件批量替换图片,并转换为智能对象?
  • Linux 逻辑卷管理
  • 深入理解Spring中的循环依赖及解决方案
  • ssh连接VirtualBox中的Ubuntu24.04(win11、putty、NAT 模式)
  • 模型蒸馏(Distillation):原理、算法、应用
  • 每日任务day0804:小小勇者成长记之药剂师的小咪
  • 深入剖析Java Stream API性能优化实践指南
  • AgxOrin平台JetPack5.x版本fix multi-cam race condition 补丁
  • (2023ICML)BLIP-2:使用冻结图像编码器和大语言模型引导语言-图像预训练
  • Ubuntu共享文件夹权限设置
  • 【数据结构初阶】--顺序表(一)
  • 使用AWS for PHP SDK实现Minio文件上传
  • nodejs 封装方法将打印日志输出到指定文件
  • mybatis-plus报错Caused by: java.sql.SQLException: 无效的列类型: 1111
  • 论文Review LIO Multi-session Voxel-SLAM | 港大MARS出品!体素+平面特征的激光SLAM!经典必读!
  • Spring Boot 应用结合 Knife4j 进行 API 分组授权管理配置
  • 【世纪龙科技】汽车自动变速器拆装虚拟实训软件
  • 国产化低代码平台如何筑牢企业数字化安全底座
  • Go语言 并发安全sync
  • Linux 磁盘管理
  • 如何选择一个容易被搜索引擎发现的域名?
  • 从零开始的云计算生活——项目实战
  • Perl 面向对象编程深入解析
  • 京东商品销量数据如何获取?API接口调用操作详解
  • AWS VPC Transit Gateway 可观测最佳实践