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中的“颜色校正”功能为参照…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
