Spring Boot集成antlr实现词法和语法分析
1.什么是antlr?
Antlr4 是一款强大的语法生成器工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。基本上是当前 Java 语言中使用最为广泛的语法生成器工具。Twitter搜索使用ANTLR进行语法分析,每天处理超过20亿次查询;Hadoop生态系统中的Hive、Pig、数据仓库和分析系统所使用的语言都用到了ANTLR;Lex Machina将ANTLR用于分析法律文本;Oracle公司在SQL开发者IDE和迁移工具中使用了ANTLR;NetBeans公司的IDE使用ANTLR来解析C++;Hibernate对象-关系映射框架(ORM)使用ANTLR来处理HQL语言
基本概念
语法分析器(parser)是用来识别语言的程序,本身包含两个部分:词法分析器(lexer)和语法分析器(parser)。词法分析阶段主要解决的关键词以及各种标识符,例如 INT、ID 等,语法分析主要是基于词法分析的结果,构造一颗语法分析树。大致的流程如下图参考2所示。

因此,为了让词法分析和语法分析能够正常工作,在使用 Antlr4 的时候,需要定义语法(grammar),这部分就是 Antlr 元语言。

使用 ANTLR4 编程的基本流程是固定的,通常分为如下三步:
-
基于需求按照 ANTLR4 的规则编写自定义语法的语义规则, 保存成以 g4 为后缀的文件。
-
使用 ANTLR4 工具处理 g4 文件,生成词法分析器、句法分析器代码、词典文件。
-
编写代码继承 Visitor 类或实现 Listener 接口,开发自己的业务逻辑代码。
Listener 模式和 Visitor 模式的区别
Listener 模式:

Visitor 模式:

- Listener 模式通过 walker 对象自行遍历,不用考虑其语法树上下级关系。Vistor 需要自行控制访问的子节点,如果遗漏了某个子节点,那么整个子节点都访问不到了。
- Listener 模式的方法没有返回值,Vistor 模式可以设定任意返回值。
- Listener 模式的访问栈清晰明确,Vistor 模式是方法调用栈,如果实现出错有可能导致 StackOverFlow。
2.代码工程
实验目的:实现基于antlr的计算器
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>ANTLR</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><antlr4.version>4.9.1</antlr4.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.antlr</groupId><artifactId>antlr4-runtime</artifactId><version>${antlr4.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.antlr</groupId><artifactId>antlr4-maven-plugin</artifactId><version>${antlr4.version}</version><configuration><sourceDirectory>src/main/java</sourceDirectory><outputDirectory>src/main/java</outputDirectory><arguments><argument>-visitor</argument><argument>-listener</argument></arguments></configuration><executions><execution><goals><goal>antlr4</goal></goals></execution></executions></plugin></plugins></build></project>
元语言LabeledExpr.g4
grammar LabeledExpr; // rename to distinguish from Expr.g4prog: 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
简单解读一下 LabeledExpr.g4 文件。ANTLR4 规则是基于正则表达式定义定义。规则的理解是自顶向下的,每个分号结束的语句表示一个规则 。例如第一行:grammar LabeledExpr; 表示我们的语法名称是 LabeledExpr, 这个名字需要跟文件名需要保持一致。Java 编码也有相似的规则:类名跟类文件一致。
- 规则 prog 表示 prog 是一个或多个 stat。
- 规则 stat 适配三种子规则:空行、表达式 expr、赋值表达式 ID’=’expr。
- 表达式 expr 适配五种子规则:乘除法、加减法、整型、ID、括号表达式。很显然,这是一个递归的定义。
最后定义的是组成复合规则的基础元素,比如:规则 ID: [a-zA-Z]+表示 ID 限于大小写英文字符串;INT: [0-9]+; 表示 INT 这个规则是 0-9 之间的一个或多个数字,当然这个定义其实并不严格。再严格一点,应该限制其长度。
在理解正则表达式的基础上,ANTLR4 的 g4 语法规则还是比较好理解的。
定义 ANTLR4 规则需要注意一种情况,即可能出现一个字符串同时支持多种规则,如以下的两个规则:
ID: [a-zA-Z]+;
FROM: ‘from’;
很明显,字符串” from”同时满足上述两个规则,ANTLR4 处理的方式是按照定义的顺序决定。这里 ID 定义在 FROM 前面,所以字符串 from 会优先匹配到 ID 这个规则上。
其实在定义好与法规中,编写完成 g4 文件后,ANTLR4 已经为我们完成了 50%的工作:帮我们实现了整个架构及接口了,剩下的开发工作就是基于接口或抽象类进行具体的实现。实现上有两种方式来处理生成的语法树,其一 Visitor 模式,另一种方式是 Listener(监听器模式)。
生成词法和语法解析器
基于maven插件生成
<plugin><groupId>org.antlr</groupId><artifactId>antlr4-maven-plugin</artifactId><version>${antlr4.version}</version><configuration><sourceDirectory>src/main/java</sourceDirectory><outputDirectory>src/main/java</outputDirectory><arguments><argument>-visitor</argument><argument>-listener</argument></arguments></configuration><executions><execution><goals><goal>antlr4</goal></goals></execution></executions>
</plugin>
执行命令
mvn antlr4:antlr4

使用ideal插件生成


实现运算逻辑
第一种:基于visitor实现
package com.et.antlr;import java.util.HashMap;
import java.util.Map;public class EvalVisitor extends LabeledExprBaseVisitor<Integer> {// Store variables (for assignment)Map<String, Integer> memory = new HashMap<>();/** stat : expr NEWLINE */@Overridepublic Integer visitPrintExpr(LabeledExprParser.PrintExprContext ctx) {Integer value = visit(ctx.expr()); // evaluate the expr child// System.out.println(value); // print the resultreturn value; // return dummy value}/** stat : 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 : 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 : 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 : INT */@Overridepublic Integer visitInt(LabeledExprParser.IntContext ctx) {return Integer.valueOf(ctx.INT().getText());}/** expr : ID */@Overridepublic Integer visitId(LabeledExprParser.IdContext ctx) {String id = ctx.ID().getText();if (memory.containsKey(id)) return memory.get(id);return 0; // default value if the variable is not found}/** expr : '(' expr ')' */@Overridepublic Integer visitParens(LabeledExprParser.ParensContext ctx) {return visit(ctx.expr()); // return child expr's value}/** stat : NEWLINE */@Overridepublic Integer visitBlank(LabeledExprParser.BlankContext ctx) {return 0; // return dummy value}
}
第二种:基于listener实现
package com.et.antlr;import org.antlr.v4.runtime.tree.ParseTreeProperty;
import org.antlr.v4.runtime.tree.TerminalNode;import java.util.HashMap;
import java.util.Map;public class EvalListener extends LabeledExprBaseListener {// Store variables (for assignment)private final Map<String, Integer> memory = new HashMap<>();// Store expression resultsprivate final ParseTreeProperty<Integer> values = new ParseTreeProperty<>();private int result=0;@Overridepublic void exitPrintExpr(LabeledExprParser.PrintExprContext ctx) {int value = values.get(ctx.expr());//System.out.println(value);result=value;}public int getResult() {return result;}@Overridepublic void exitAssign(LabeledExprParser.AssignContext ctx) {String id = ctx.ID().getText();int value = values.get(ctx.expr());memory.put(id, value);}@Overridepublic void exitMulDiv(LabeledExprParser.MulDivContext ctx) {int left = values.get(ctx.expr(0));int right = values.get(ctx.expr(1));if (ctx.op.getType() == LabeledExprParser.MUL) {values.put(ctx, left * right);} else {values.put(ctx, left / right);}}@Overridepublic void exitAddSub(LabeledExprParser.AddSubContext ctx) {int left = values.get(ctx.expr(0));int right = values.get(ctx.expr(1));if (ctx.op.getType() == LabeledExprParser.ADD) {values.put(ctx, left + right);} else {values.put(ctx, left - right);}}@Overridepublic void exitInt(LabeledExprParser.IntContext ctx) {int value = Integer.parseInt(ctx.INT().getText());values.put(ctx, value);}@Overridepublic void exitId(LabeledExprParser.IdContext ctx) {String id = ctx.ID().getText();if (memory.containsKey(id)) {values.put(ctx, memory.get(id));} else {values.put(ctx, 0); // default value if the variable is not found}}@Overridepublic void exitParens(LabeledExprParser.ParensContext ctx) {values.put(ctx, values.get(ctx.expr()));}
}
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
- https://github.com/Harries/springboot-demo
3.测试
测试vistor方式
package com.et.antlr; /**** Excerpted from "The Definitive ANTLR 4 Reference",* published by The Pragmatic Bookshelf.* Copyrights apply to this code. It may not be used to create training material, * courses, books, articles, and the like. Contact us if you are in doubt.* We make no guarantees that this code is fit for any purpose. * Visit http://www.pragmaticprogrammer.com/titles/tpantlr2 for more book information.
***/
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;import java.io.FileInputStream;
import java.io.InputStream;public class CalcByVisit {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("1+2*3\n");LabeledExprLexer lexer = new LabeledExprLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);LabeledExprParser parser = new LabeledExprParser(tokens);ParseTree tree = parser.prog(); // parseEvalVisitor eval = new EvalVisitor();int result =eval.visit(tree);System.out.println(result);}
}
测试listener方式
package com.et.antlr;import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;/*** @author liuhaihua* @version 1.0* @ClassName CalbyLisenter* @Description todo* @date 2024年06月06日 16:40*/public class CalbyLisener {public static void main(String[] args) throws IOException {/* 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("1+2*3\n");LabeledExprLexer lexer = new LabeledExprLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);LabeledExprParser parser = new LabeledExprParser(tokens);ParseTree tree = parser.prog(); // parseParseTreeWalker walker = new ParseTreeWalker();EvalListener evalListener =new EvalListener();walker.walk(evalListener, tree);int result=evalListener.getResult();System.out.println(result);}
}
运行上述测试用例,计算结果符合预期
4.引用
- ANTLR
- Spring Boot集成antlr实现词法和语法分析 | Harries Blog™
相关文章:
Spring Boot集成antlr实现词法和语法分析
1.什么是antlr? Antlr4 是一款强大的语法生成器工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。基本上是当前 Java 语言中使用最为广泛的语法生成器工具。Twitter搜索使用ANTLR进行语法分析,每天处理超过20亿次查询࿱…...
多线程中run()和start()的区别
我们知道,在多线程中 Thread thread new Thread(runnable); thread.start();以及 thread.run();都可以执行runnable中run方法下的代码,但是二者又有所不同 下面给出一段代码用以体现二者的区别: 以下代码中,通过thread.start()启…...
Nginx基础理论
Nginx最为最受欢迎的反向代理和负载均衡服务器,被广泛的应用于互联网项目中。这不仅仅是因为Nginx本身比较轻量,更多的是得益于Nginx的高性能特性,以及支持插件化开发,为此,很多开发者或者公司基于Nginx开发出了众多的…...
【QT5】<应用> 小游戏:贪吃蛇
文章目录 一、项目要求 二、需求分析 三、实现效果 四、代码 一、项目要求 【1】主要实现:游戏界面存在一条蛇🐍,使用键盘wsad或者↑↓←→键盘可以控制蛇的行走方向。同时界面中会随机出现食物,蛇可以吃食物,然后…...
【Webpack】使用 Webpack 构建 Vue3+TS 项目
构建项目目录 tsc --init npm init -yshim.d.ts 文件是一个类型声明文件,用于告诉 TypeScript 编译器如何处理 Vue 的单文件组件(SFC)和其他自定义模块。为 Vue 的单文件组件和其他非 TypeScript 模块提供类型信息,以便在 TypeScr…...
数据防泄漏的六个步骤|数据防泄漏软件有哪些
在当前复杂多变的网络安全环境下,数据防泄漏软件成为了企业信息安全架构中不可或缺的一环。下面以安企神软件为例,告诉你怎么防止数据泄露,以及好用的防泄露软件。 1. 安企神软件 安企神软件是当前市场上备受推崇的企业级数据防泄漏解决方案…...
SpringCloud 网关Gateway配置并使用
目录 1 什么是网关? 2 Gateway的使用 2.1 在其pom文件中引入依赖 2.2 然后gateway配置文件中配置信息 2.3 启动网关微服务 3 网关处理流程 4 前端-网关-微服务-微服务间实现信息共享传递 1 什么是网关? 网关:就是网络的关口ÿ…...
MySQl基础----Linux下搭建mysql软件及登录和基本使用(附实操图超简单一看就会)
绪论 涓滴之水可磨损大石,不是由于他力量强大,而是由于昼夜不舍地滴坠。 只有勤奋不懈地努力,才能够获得那些技巧。 ——贝多芬。新开MySQL篇章,本章非常基础包括如何在Linux上搭建(当然上面的SQL语句你在其他能执行…...
PostgreSQL17优化器改进(4)允许UNION(没有ALL)使用MergeAppend
PostgreSQL17优化器改进(4)允许UNION(没有ALL)使用MergeAppend UNION存在的问题 到PostgreSQL16.3版本为止,UNION执行计划通常不是最优的,优化器有两种处理方法: 优化器只考虑使用Append节点并通过使用Hash Aggregate,Append -…...
SSM 基于大数据技术的创业推荐系统-计算机毕业设计源码02979
摘 要 科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代。在现实运用中,应用软件的工作…...
基于WPF技术的换热站智能监控系统03--实现左侧加载动画
1、左侧布局规划 左侧分5行,每行的高度通过height属性来指定,1.2*表示占1.2倍的宽度 2、创建用户控件 在WPF中想要进行个性化处理,主要可以通过三个方面来实现:控件模板(控件模板、数据模板、数据容器模板)…...
4D毫米波雷达技术及发展
文章目录 前言一、4D毫米波雷达是什么?二、毫米波雷达是什么?毫米波雷达的基本原理多普勒效应 前言 现阶段自动驾驶技术中,主要用到的传感器有摄像头、激光雷达和毫米波雷达。 摄像头的光谱从可见光到红外光谱,是最接近人眼的传感…...
请解释Java Web应用的开发流程,包括前后端分离和交互方式。请解释Java中的锁分离技术,并讨论其在提高并发性能方面的作用。
请解释Java Web应用的开发流程,包括前后端分离和交互方式。 Java Web应用的开发流程是一个涵盖多个阶段的过程,这些阶段从需求分析开始,经过设计、编码、测试,最终到部署和维护。在这个过程中,前后端分离成为现代Web应…...
selenium使用已经打开的浏览器
Selenium 本身不支持直接连接到一个已经打开的浏览器页面。Selenium 启动的浏览器实例是一个全新的会话,它与手动打开的浏览器页面是分开的。但是,有一些变通的方法可以实现类似的效果。 一种方法是通过附加代理连接到已经打开的浏览器。下面是如何实现…...
Redis: 深入解析高性能内存数据库的实现原理
一、Redis简介 Redis是一种基于内存的键值存储数据库,支持丰富的数据类型,如字符串、列表、集合、有序集合和哈希表。它不仅具有极高的性能,还支持数据持久化、主从复制和分布式架构,使其在各种应用场景中表现出色。 1.1 Redis的…...
使用 Python进行自动备份文件
文件备份对数据保护至关重要,让我们使用 shutil 模块创建一个简单的备份脚本 这段代码的作用就是将指定源目录中的所有文件备份到目标备份目录中,并在备份目录中创建带有时间戳的子目录,通过定期运行这段代码,可以实现自动备份文…...
02_01_SpringMVC初识
一、回顾MVC三层架构 1、什么是MVC三层 MVC是 模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。主要作用是降低视图与业务逻辑之间的双向耦合,它不是一种…...
Python学习打卡:day04
day4 笔记来源于:黑马程序员python教程,8天python从入门到精通,学python看这套就够了 目录 day428、while 循环的嵌套应用29、while 循环案例 — 九九乘法表补充知识示例:九九乘法表 30、for 循环基本语法while 和 for 循环对比f…...
gitlab问题记录
You wont be able to pull or push project code via SSH until you add an SSH key to you 解决方案:https://blog.csdn.net/gufenchen/article/details/95663284...
OpenCV练习(1)签名修复
1.目的 在学校的学习过程中,需要递交许多材料,且每份材料上都需要对应负责人签名,有时候找别人要签名,然后自己粘贴的话,会出现签名模糊,背景不是纯白透明。为此以word中的“颜色校正”功能为参照…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
